sparkecoder 0.1.83 → 0.1.85

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.
Files changed (136) hide show
  1. package/dist/agent/index.js +125 -22
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +532 -395
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +532 -395
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +532 -395
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/tools/index.d.ts +19 -36
  10. package/dist/tools/index.js +99 -10
  11. package/dist/tools/index.js.map +1 -1
  12. package/package.json +1 -1
  13. package/web/.next/BUILD_ID +1 -1
  14. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  15. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  16. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  17. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  18. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  20. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  22. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  23. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  38. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  39. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  77. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  80. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  83. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  85. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  86. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_02a118f9._.js → 2374f_00f7fe07._.js} +1 -1
  87. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ad08e83a._.js → 2374f_2801b766._.js} +1 -1
  88. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_0ed477f8._.js → 2374f_369747ce._.js} +1 -1
  89. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_3b51a934._.js → 2374f_60d8842c._.js} +1 -1
  90. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_acf3dfe4._.js → 2374f_806bd012._.js} +1 -1
  91. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db3e363b._.js → 2374f_8dc0f9aa._.js} +1 -1
  92. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f0d7e130._.js → 2374f_9adc1edb._.js} +1 -1
  93. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5ebfcf1a._.js → 2374f_b7f45fdf._.js} +1 -1
  94. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_12bad06e._.js → 2374f_c13c8f4f._.js} +1 -1
  95. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fc992d90._.js → 2374f_cc6c6363._.js} +1 -1
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_3e519469._.js → 2374f_d58d0276._.js} +1 -1
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_a0f483d1._.js → 2374f_ecd2bdca._.js} +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c1d54c16._.js → 2374f_f363c084._.js} +1 -1
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2526ca80._.js → 2374f_fdfc7f3d._.js} +1 -1
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__06818a54._.js → [root-of-the-server]__25b25c9d._.js} +2 -2
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__a1877334._.js → [root-of-the-server]__9d3a7cbf._.js} +4 -4
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_cc5f7515._.js → web_08242997._.js} +2 -2
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_2b3a5919._.js → web_123ffe97._.js} +2 -2
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_38156da8._.js → web_99b01335._.js} +2 -2
  105. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  106. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  107. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  108. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  109. package/web/.next/standalone/web/.next/static/chunks/{f95d41079838994a.js → 2624c966c288fd41.js} +3 -3
  110. package/web/.next/standalone/web/.next/static/chunks/74b64476a24dd71e.css +1 -0
  111. package/web/.next/standalone/web/.next/static/{static/chunks/fc39a194539da104.js → chunks/8af263bc97c0c9ee.js} +1 -1
  112. package/web/.next/{static/chunks/2cafc7cb79454d33.js → standalone/web/.next/static/chunks/cfadc93a98190e5a.js} +1 -1
  113. package/web/.next/standalone/web/.next/static/static/chunks/{f95d41079838994a.js → 2624c966c288fd41.js} +3 -3
  114. package/web/.next/standalone/web/.next/static/static/chunks/74b64476a24dd71e.css +1 -0
  115. package/web/.next/{static/chunks/fc39a194539da104.js → standalone/web/.next/static/static/chunks/8af263bc97c0c9ee.js} +1 -1
  116. package/web/.next/standalone/web/.next/static/{chunks/2cafc7cb79454d33.js → static/chunks/cfadc93a98190e5a.js} +1 -1
  117. package/web/.next/standalone/web/src/components/ai-elements/todo-panel.tsx +194 -110
  118. package/web/.next/standalone/web/src/components/ai-elements/todo-tool.tsx +78 -1
  119. package/web/.next/standalone/web/src/components/chat-interface.tsx +15 -9
  120. package/web/.next/standalone/web/src/lib/api.ts +17 -0
  121. package/web/.next/static/chunks/{f95d41079838994a.js → 2624c966c288fd41.js} +3 -3
  122. package/web/.next/static/chunks/74b64476a24dd71e.css +1 -0
  123. package/web/.next/{standalone/web/.next/static/chunks/fc39a194539da104.js → static/chunks/8af263bc97c0c9ee.js} +1 -1
  124. package/web/.next/{standalone/web/.next/static/static/chunks/2cafc7cb79454d33.js → static/chunks/cfadc93a98190e5a.js} +1 -1
  125. package/web/.next/standalone/web/.next/static/chunks/41a5c049931b2c77.css +0 -1
  126. package/web/.next/standalone/web/.next/static/static/chunks/41a5c049931b2c77.css +0 -1
  127. package/web/.next/static/chunks/41a5c049931b2c77.css +0 -1
  128. /package/web/.next/standalone/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_buildManifest.js +0 -0
  129. /package/web/.next/standalone/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_clientMiddlewareManifest.json +0 -0
  130. /package/web/.next/standalone/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_ssgManifest.js +0 -0
  131. /package/web/.next/standalone/web/.next/static/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_buildManifest.js +0 -0
  132. /package/web/.next/standalone/web/.next/static/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_clientMiddlewareManifest.json +0 -0
  133. /package/web/.next/standalone/web/.next/static/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_ssgManifest.js +0 -0
  134. /package/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_buildManifest.js +0 -0
  135. /package/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_clientMiddlewareManifest.json +0 -0
  136. /package/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { cn } from "@/lib/utils";
4
- import { type TodoItem, type TodosResponse } from "@/lib/api";
4
+ import { type TodoItem, type TodosResponse, type PlanItem } from "@/lib/api";
5
5
  import {
6
6
  CheckCircle2Icon,
7
7
  CircleIcon,
@@ -10,6 +10,8 @@ import {
10
10
  ChevronDownIcon,
11
11
  ChevronRightIcon,
12
12
  ListTodoIcon,
13
+ FileTextIcon,
14
+ XIcon,
13
15
  } from "lucide-react";
14
16
  import { useState } from "react";
15
17
  import {
@@ -20,6 +22,7 @@ import {
20
22
 
21
23
  export interface TodoPanelProps {
22
24
  todosData: TodosResponse | null;
25
+ plans?: PlanItem[];
23
26
  isLoading?: boolean;
24
27
  className?: string;
25
28
  }
@@ -38,14 +41,20 @@ const statusColors: Record<string, string> = {
38
41
  cancelled: "text-muted-foreground/50",
39
42
  };
40
43
 
41
- export function TodoPanel({ todosData, isLoading, className }: TodoPanelProps) {
44
+ export function TodoPanel({ todosData, plans, isLoading, className }: TodoPanelProps) {
42
45
  const [isExpanded, setIsExpanded] = useState(false);
46
+ const [viewingPlan, setViewingPlan] = useState<PlanItem | null>(null);
47
+
48
+ const hasTodos = todosData && todosData.todos.length > 0;
49
+ const hasPlans = plans && plans.length > 0;
43
50
 
44
- if (!todosData || todosData.todos.length === 0) {
51
+ if (!hasTodos && !hasPlans) {
45
52
  return null;
46
53
  }
47
54
 
48
- const { todos, stats, nextTodo } = todosData;
55
+ const todos = todosData?.todos || [];
56
+ const stats = todosData?.stats || { total: 0, pending: 0, inProgress: 0, completed: 0, cancelled: 0 };
57
+ const nextTodo = todosData?.nextTodo || null;
49
58
 
50
59
  // Sort: in_progress first, then pending, then completed/cancelled
51
60
  const sortedTodos = [...todos].sort((a, b) => {
@@ -58,121 +67,196 @@ export function TodoPanel({ todosData, isLoading, className }: TodoPanelProps) {
58
67
  : 0;
59
68
 
60
69
  return (
61
- <div className={cn(
62
- "rounded-lg border bg-card/50 backdrop-blur-sm overflow-hidden",
63
- className
64
- )}>
65
- {/* Header - always shows current task and stats */}
66
- <button
67
- onClick={() => setIsExpanded(!isExpanded)}
68
- className="flex items-center justify-between w-full px-3 py-2 hover:bg-muted/50 transition-colors"
69
- >
70
- <div className="flex items-center gap-2 min-w-0 flex-1">
71
- <ListTodoIcon className="size-4 text-primary shrink-0" />
72
- {/* Show current task inline when collapsed */}
73
- {!isExpanded && nextTodo ? (
74
- <div className="flex items-center gap-2 min-w-0 flex-1">
75
- <CircleDotIcon className="size-3.5 text-primary shrink-0" />
76
- <span className="text-sm truncate">{nextTodo.content}</span>
77
- </div>
78
- ) : (
79
- <span className="font-medium text-sm">Tasks</span>
80
- )}
81
- <span className="text-xs text-muted-foreground shrink-0">
82
- {stats.completed}/{stats.total}
83
- </span>
84
- </div>
85
- <div className="flex items-center gap-2 shrink-0">
86
- {/* Progress bar */}
87
- <div className="w-16 h-1.5 bg-muted rounded-full overflow-hidden">
88
- <div
89
- className="h-full bg-primary transition-all duration-300"
90
- style={{ width: `${progressPercent}%` }}
91
- />
70
+ <>
71
+ <div className={cn(
72
+ "rounded-lg border bg-card/50 backdrop-blur-sm overflow-hidden",
73
+ className
74
+ )}>
75
+ {/* Header - always shows current task and stats */}
76
+ <button
77
+ onClick={() => setIsExpanded(!isExpanded)}
78
+ className="flex items-center justify-between w-full px-3 py-2 hover:bg-muted/50 transition-colors"
79
+ >
80
+ <div className="flex items-center gap-2 min-w-0 flex-1">
81
+ <ListTodoIcon className="size-4 text-primary shrink-0" />
82
+ {/* Show current task inline when collapsed */}
83
+ {!isExpanded && nextTodo ? (
84
+ <div className="flex items-center gap-2 min-w-0 flex-1">
85
+ <CircleDotIcon className="size-3.5 text-primary shrink-0" />
86
+ <span className="text-sm truncate">{nextTodo.content}</span>
87
+ </div>
88
+ ) : (
89
+ <span className="font-medium text-sm">Tasks</span>
90
+ )}
91
+ {stats.total > 0 && (
92
+ <span className="text-xs text-muted-foreground shrink-0">
93
+ {stats.completed}/{stats.total}
94
+ </span>
95
+ )}
96
+ {hasPlans && (
97
+ <span className="text-xs text-muted-foreground shrink-0 flex items-center gap-1">
98
+ <FileTextIcon className="size-3" />
99
+ {plans!.length} plan{plans!.length !== 1 ? 's' : ''}
100
+ </span>
101
+ )}
92
102
  </div>
93
- {isExpanded ? (
94
- <ChevronDownIcon className="size-4 text-muted-foreground" />
95
- ) : (
96
- <ChevronRightIcon className="size-4 text-muted-foreground" />
97
- )}
98
- </div>
99
- </button>
100
-
101
- {isExpanded && (
102
- <div className="border-t">
103
- {/* Next todo highlight */}
104
- {nextTodo && (
105
- <div className="px-3 py-2 bg-primary/5 border-b flex items-center gap-2">
106
- <CircleDotIcon className="size-4 text-primary shrink-0" />
107
- <div className="min-w-0 flex-1">
108
- <span className="text-xs text-primary font-medium uppercase tracking-wide">
109
- Current
110
- </span>
111
- <p className="text-sm font-medium truncate">{nextTodo.content}</p>
103
+ <div className="flex items-center gap-2 shrink-0">
104
+ {stats.total > 0 && (
105
+ <div className="w-16 h-1.5 bg-muted rounded-full overflow-hidden">
106
+ <div
107
+ className="h-full bg-primary transition-all duration-300"
108
+ style={{ width: `${progressPercent}%` }}
109
+ />
112
110
  </div>
113
- </div>
114
- )}
115
-
116
- {/* Todo list */}
117
- <div className="max-h-[200px] overflow-auto">
118
- {sortedTodos.map((todo) => {
119
- const Icon = statusIcons[todo.status] || CircleIcon;
120
- const color = statusColors[todo.status] || "text-muted-foreground";
121
- const isNext = todo.id === nextTodo?.id;
122
-
123
- return (
124
- <Tooltip key={todo.id}>
125
- <TooltipTrigger asChild>
126
- <div
127
- className={cn(
128
- "flex items-start gap-2 px-3 py-1.5 hover:bg-muted/30 transition-colors cursor-default",
129
- isNext && "bg-primary/5"
130
- )}
131
- >
132
- <Icon className={cn("size-4 mt-0.5 shrink-0", color)} />
133
- <span className={cn(
134
- "text-sm flex-1 min-w-0 truncate",
135
- todo.status === "cancelled" && "line-through text-muted-foreground/50",
136
- todo.status === "completed" && "text-muted-foreground"
137
- )}>
138
- {todo.content}
139
- </span>
140
- </div>
141
- </TooltipTrigger>
142
- <TooltipContent side="left" className="max-w-xs">
143
- <p className="text-sm">{todo.content}</p>
144
- <p className="text-xs text-muted-foreground capitalize mt-1">
145
- {todo.status.replace('_', ' ')}
146
- </p>
147
- </TooltipContent>
148
- </Tooltip>
149
- );
150
- })}
111
+ )}
112
+ {isExpanded ? (
113
+ <ChevronDownIcon className="size-4 text-muted-foreground" />
114
+ ) : (
115
+ <ChevronRightIcon className="size-4 text-muted-foreground" />
116
+ )}
151
117
  </div>
118
+ </button>
152
119
 
153
- {/* Stats footer */}
154
- <div className="px-3 py-1.5 border-t bg-muted/30 flex items-center gap-3 text-xs text-muted-foreground">
155
- {stats.inProgress > 0 && (
156
- <span className="flex items-center gap-1">
157
- <CircleDotIcon className="size-3 text-primary" />
158
- {stats.inProgress} active
159
- </span>
120
+ {isExpanded && (
121
+ <div className="border-t">
122
+ {/* Plans section */}
123
+ {hasPlans && (
124
+ <div className="border-b">
125
+ {plans!.map((plan) => (
126
+ <button
127
+ key={plan.name}
128
+ onClick={() => setViewingPlan(plan)}
129
+ className="flex items-center gap-2 w-full px-3 py-1.5 hover:bg-muted/30 transition-colors text-left"
130
+ >
131
+ <FileTextIcon className="size-4 text-blue-500 shrink-0" />
132
+ <span className="text-sm font-medium flex-1 min-w-0 truncate">{plan.name}</span>
133
+ <span className="text-xs text-muted-foreground shrink-0">View</span>
134
+ </button>
135
+ ))}
136
+ </div>
160
137
  )}
161
- {stats.pending > 0 && (
162
- <span className="flex items-center gap-1">
163
- <CircleIcon className="size-3" />
164
- {stats.pending} pending
165
- </span>
138
+
139
+ {/* Next todo highlight */}
140
+ {nextTodo && (
141
+ <div className="px-3 py-2 bg-primary/5 border-b flex items-center gap-2">
142
+ <CircleDotIcon className="size-4 text-primary shrink-0" />
143
+ <div className="min-w-0 flex-1">
144
+ <span className="text-xs text-primary font-medium uppercase tracking-wide">
145
+ Current
146
+ </span>
147
+ <p className="text-sm font-medium truncate">{nextTodo.content}</p>
148
+ </div>
149
+ </div>
166
150
  )}
167
- {stats.completed > 0 && (
168
- <span className="flex items-center gap-1">
169
- <CheckCircle2Icon className="size-3 text-green-500" />
170
- {stats.completed} done
171
- </span>
151
+
152
+ {/* Todo list */}
153
+ {sortedTodos.length > 0 && (
154
+ <div className="max-h-[200px] overflow-auto">
155
+ {sortedTodos.map((todo) => {
156
+ const Icon = statusIcons[todo.status] || CircleIcon;
157
+ const color = statusColors[todo.status] || "text-muted-foreground";
158
+ const isNext = todo.id === nextTodo?.id;
159
+
160
+ return (
161
+ <Tooltip key={todo.id}>
162
+ <TooltipTrigger asChild>
163
+ <div
164
+ className={cn(
165
+ "flex items-start gap-2 px-3 py-1.5 hover:bg-muted/30 transition-colors cursor-default",
166
+ isNext && "bg-primary/5"
167
+ )}
168
+ >
169
+ <Icon className={cn("size-4 mt-0.5 shrink-0", color)} />
170
+ <span className={cn(
171
+ "text-sm flex-1 min-w-0 truncate",
172
+ todo.status === "cancelled" && "line-through text-muted-foreground/50",
173
+ todo.status === "completed" && "text-muted-foreground"
174
+ )}>
175
+ {todo.content}
176
+ </span>
177
+ </div>
178
+ </TooltipTrigger>
179
+ <TooltipContent side="left" className="max-w-xs">
180
+ <p className="text-sm">{todo.content}</p>
181
+ <p className="text-xs text-muted-foreground capitalize mt-1">
182
+ {todo.status.replace('_', ' ')}
183
+ </p>
184
+ </TooltipContent>
185
+ </Tooltip>
186
+ );
187
+ })}
188
+ </div>
189
+ )}
190
+
191
+ {/* Stats footer */}
192
+ {stats.total > 0 && (
193
+ <div className="px-3 py-1.5 border-t bg-muted/30 flex items-center gap-3 text-xs text-muted-foreground">
194
+ {stats.inProgress > 0 && (
195
+ <span className="flex items-center gap-1">
196
+ <CircleDotIcon className="size-3 text-primary" />
197
+ {stats.inProgress} active
198
+ </span>
199
+ )}
200
+ {stats.pending > 0 && (
201
+ <span className="flex items-center gap-1">
202
+ <CircleIcon className="size-3" />
203
+ {stats.pending} pending
204
+ </span>
205
+ )}
206
+ {stats.completed > 0 && (
207
+ <span className="flex items-center gap-1">
208
+ <CheckCircle2Icon className="size-3 text-green-500" />
209
+ {stats.completed} done
210
+ </span>
211
+ )}
212
+ </div>
172
213
  )}
173
214
  </div>
215
+ )}
216
+ </div>
217
+
218
+ {/* Plan viewer modal */}
219
+ {viewingPlan && (
220
+ <div
221
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
222
+ onClick={() => setViewingPlan(null)}
223
+ >
224
+ <div
225
+ className="bg-background rounded-xl border shadow-2xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4"
226
+ onClick={(e) => e.stopPropagation()}
227
+ >
228
+ {/* Modal header */}
229
+ <div className="flex items-center justify-between px-5 py-4 border-b shrink-0">
230
+ <div className="flex items-center gap-2 min-w-0">
231
+ <FileTextIcon className="size-5 text-blue-500 shrink-0" />
232
+ <h2 className="font-semibold text-lg truncate">{viewingPlan.name}</h2>
233
+ </div>
234
+ <button
235
+ onClick={() => setViewingPlan(null)}
236
+ className="p-1 rounded-md hover:bg-muted transition-colors shrink-0"
237
+ >
238
+ <XIcon className="size-5" />
239
+ </button>
240
+ </div>
241
+ {/* Modal body — markdown content */}
242
+ <div className="overflow-auto flex-1 px-5 py-4">
243
+ <div className="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap font-mono text-sm leading-relaxed">
244
+ {viewingPlan.content}
245
+ </div>
246
+ </div>
247
+ {/* Modal footer */}
248
+ <div className="px-5 py-3 border-t bg-muted/30 flex items-center justify-between text-xs text-muted-foreground shrink-0">
249
+ <span>{viewingPlan.sizeChars.toLocaleString()} characters</span>
250
+ <button
251
+ onClick={() => setViewingPlan(null)}
252
+ className="px-3 py-1.5 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors"
253
+ >
254
+ Close
255
+ </button>
256
+ </div>
257
+ </div>
174
258
  </div>
175
259
  )}
176
- </div>
260
+ </>
177
261
  );
178
262
  }
@@ -13,6 +13,10 @@ import {
13
13
  TrashIcon,
14
14
  XCircleIcon,
15
15
  ClockIcon,
16
+ FileTextIcon,
17
+ FolderOpenIcon,
18
+ EyeIcon,
19
+ Trash2Icon,
16
20
  } from "lucide-react";
17
21
  import { useState } from "react";
18
22
 
@@ -25,10 +29,12 @@ export interface TodoItem {
25
29
  }
26
30
 
27
31
  export interface TodoInput {
28
- action: "add" | "list" | "mark" | "clear";
32
+ action: "add" | "list" | "mark" | "clear" | "save_plan" | "list_plans" | "get_plan" | "delete_plan";
29
33
  items?: { content: string; order?: number }[];
30
34
  todoId?: string;
31
35
  status?: "pending" | "in_progress" | "completed" | "cancelled";
36
+ planName?: string;
37
+ planContent?: string;
32
38
  }
33
39
 
34
40
  export interface TodoOutput {
@@ -46,6 +52,14 @@ export interface TodoOutput {
46
52
  completed: number;
47
53
  cancelled: number;
48
54
  };
55
+ planName?: string;
56
+ filename?: string;
57
+ sizeChars?: number;
58
+ content?: string;
59
+ plans?: Array<{ name: string; title: string; filename: string; sizeChars: number }>;
60
+ count?: number;
61
+ autoCreatedTodos?: TodoItem[];
62
+ autoCreatedFromPhase?: string;
49
63
  }
50
64
 
51
65
  export interface TodoToolProps {
@@ -74,6 +88,10 @@ const actionLabels: Record<string, string> = {
74
88
  list: "List Tasks",
75
89
  mark: "Update Task",
76
90
  clear: "Clear Tasks",
91
+ save_plan: "Save Plan",
92
+ list_plans: "List Plans",
93
+ get_plan: "View Plan",
94
+ delete_plan: "Delete Plan",
77
95
  };
78
96
 
79
97
  const ActionIcon = ({ action }: { action: string }) => {
@@ -86,6 +104,14 @@ const ActionIcon = ({ action }: { action: string }) => {
86
104
  return <CheckCircleIcon className="size-3.5" />;
87
105
  case "clear":
88
106
  return <TrashIcon className="size-3.5" />;
107
+ case "save_plan":
108
+ return <FileTextIcon className="size-3.5" />;
109
+ case "list_plans":
110
+ return <FolderOpenIcon className="size-3.5" />;
111
+ case "get_plan":
112
+ return <EyeIcon className="size-3.5" />;
113
+ case "delete_plan":
114
+ return <Trash2Icon className="size-3.5" />;
89
115
  default:
90
116
  return <ListTodoIcon className="size-3.5" />;
91
117
  }
@@ -225,6 +251,57 @@ export function TodoTool({
225
251
  </div>
226
252
  )}
227
253
 
254
+ {/* Plan actions output */}
255
+ {output?.action === "save_plan" && output.planName && (
256
+ <div className="px-3 py-2 space-y-1">
257
+ <div className="flex items-center gap-2 text-sm">
258
+ <FileTextIcon className="size-4 text-blue-500 shrink-0" />
259
+ <span className="font-medium">{output.planName}</span>
260
+ <span className="text-xs text-muted-foreground">
261
+ ({(output.sizeChars ?? 0).toLocaleString()} chars)
262
+ </span>
263
+ </div>
264
+ {output.autoCreatedTodos && output.autoCreatedTodos.length > 0 && (
265
+ <div className="mt-2 pt-2 border-t">
266
+ <span className="text-xs text-green-600 dark:text-green-400 font-medium">
267
+ Auto-created {output.autoCreatedTodos.length} todo(s) from plan
268
+ </span>
269
+ </div>
270
+ )}
271
+ </div>
272
+ )}
273
+ {output?.action === "list_plans" && output.plans && (
274
+ <div className="px-3 py-2 max-h-[200px] overflow-auto">
275
+ {output.plans.length === 0 ? (
276
+ <p className="text-sm text-muted-foreground">No plans yet</p>
277
+ ) : (
278
+ output.plans.map((plan) => (
279
+ <div key={plan.name} className="flex items-center gap-2 py-1">
280
+ <FileTextIcon className="size-4 text-blue-500 shrink-0" />
281
+ <span className="text-sm font-medium">{plan.title || plan.name}</span>
282
+ <span className="text-xs text-muted-foreground">
283
+ ({plan.sizeChars.toLocaleString()} chars)
284
+ </span>
285
+ </div>
286
+ ))
287
+ )}
288
+ </div>
289
+ )}
290
+ {output?.action === "get_plan" && output.content && (
291
+ <div className="px-3 py-2 max-h-[300px] overflow-auto">
292
+ <pre className="text-xs font-mono whitespace-pre-wrap leading-relaxed text-muted-foreground">
293
+ {output.content.length > 2000
294
+ ? output.content.slice(0, 2000) + '\n... (truncated in UI)'
295
+ : output.content}
296
+ </pre>
297
+ </div>
298
+ )}
299
+ {output?.action === "delete_plan" && output.planName && (
300
+ <div className="px-3 py-2 text-sm text-muted-foreground">
301
+ Deleted plan: {output.planName}
302
+ </div>
303
+ )}
304
+
228
305
  {/* Empty state */}
229
306
  {action === "list" && output?.items?.length === 0 && (
230
307
  <div className="px-3 py-4 text-center text-muted-foreground text-sm">
@@ -97,6 +97,8 @@ import {
97
97
  updateSession,
98
98
  updateToolApproval,
99
99
  getSessionTodos,
100
+ getSessionPlans,
101
+ type PlanItem,
100
102
  getSessionCheckpoints,
101
103
  revertToCheckpoint,
102
104
  checkVersion,
@@ -403,6 +405,7 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
403
405
  const [currentToolCalls, setCurrentToolCalls] = useState<ToolCallInfo[]>([]);
404
406
  const [pendingApprovals, setPendingApprovals] = useState<PendingApproval[]>([]);
405
407
  const [todosData, setTodosData] = useState<TodosResponse | null>(null);
408
+ const [plansData, setPlansData] = useState<PlanItem[]>([]);
406
409
  const [checkpoints, setCheckpoints] = useState<Checkpoint[]>([]);
407
410
  const [isReverting, setIsReverting] = useState(false);
408
411
  const currentTextRef = useRef('');
@@ -1621,21 +1624,24 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
1621
1624
  let isStale = false;
1622
1625
  const currentSessionId = session.id;
1623
1626
 
1624
- const checkTodos = async () => {
1627
+ const checkTodosAndPlans = async () => {
1625
1628
  try {
1626
- const data = await getSessionTodos(currentSessionId);
1627
- // Don't update state if session changed during async work
1629
+ const [todosResult, plansResult] = await Promise.all([
1630
+ getSessionTodos(currentSessionId),
1631
+ getSessionPlans(currentSessionId).catch(() => ({ plans: [] as PlanItem[] })),
1632
+ ]);
1628
1633
  if (isStale) return;
1629
- setTodosData(data);
1634
+ setTodosData(todosResult);
1635
+ setPlansData(plansResult.plans || []);
1630
1636
  } catch {
1631
1637
  // Ignore errors
1632
1638
  }
1633
1639
  };
1634
1640
  // Initial fetch
1635
- checkTodos();
1641
+ checkTodosAndPlans();
1636
1642
  // Poll every 2 seconds when running, 5 seconds otherwise
1637
1643
  const pollInterval = isRunning ? 1000 : 5000;
1638
- const interval = setInterval(checkTodos, pollInterval);
1644
+ const interval = setInterval(checkTodosAndPlans, pollInterval);
1639
1645
  return () => {
1640
1646
  isStale = true;
1641
1647
  clearInterval(interval);
@@ -2813,10 +2819,10 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
2813
2819
  </div>
2814
2820
  )}
2815
2821
 
2816
- {/* Todo Panel - hidden in embed mode */}
2817
- {!isEmbed && todosData && todosData.todos && todosData.todos.length > 0 && (
2822
+ {/* Todo Panel + Plans - hidden in embed mode */}
2823
+ {!isEmbed && ((todosData && todosData.todos && todosData.todos.length > 0) || plansData.length > 0) && (
2818
2824
  <div className="px-4 py-2 border-b border-border/50 bg-muted/20">
2819
- <TodoPanel todosData={todosData} />
2825
+ <TodoPanel todosData={todosData} plans={plansData} />
2820
2826
  </div>
2821
2827
  )}
2822
2828
 
@@ -187,6 +187,23 @@ export async function getSessionTodos(sessionId: string): Promise<TodosResponse>
187
187
  return res.json();
188
188
  }
189
189
 
190
+ export interface PlanItem {
191
+ name: string;
192
+ content: string;
193
+ sizeChars: number;
194
+ }
195
+
196
+ export interface PlansResponse {
197
+ sessionId: string;
198
+ plans: PlanItem[];
199
+ count: number;
200
+ }
201
+
202
+ export async function getSessionPlans(sessionId: string): Promise<PlansResponse> {
203
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/plans`);
204
+ return res.json();
205
+ }
206
+
190
207
  export interface PendingInputResponse {
191
208
  hasPendingInput: boolean;
192
209
  text: string | null;