sparkecoder 0.1.36 → 0.1.38

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 (125) hide show
  1. package/dist/agent/index.d.ts +2 -2
  2. package/dist/agent/index.js +4 -4
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +92 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/{index-BSbFzE22.d.ts → index-D2eLXP9V.d.ts} +1 -1
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +92 -4
  9. package/dist/index.js.map +1 -1
  10. package/dist/{search-CrICQDSa.d.ts → search-D3Wtf7TJ.d.ts} +1 -1
  11. package/dist/server/index.js +92 -4
  12. package/dist/server/index.js.map +1 -1
  13. package/dist/tools/index.d.ts +2 -2
  14. package/dist/tools/index.js +4 -4
  15. package/dist/tools/index.js.map +1 -1
  16. package/package.json +1 -1
  17. package/web/.next/BUILD_ID +1 -1
  18. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  20. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  21. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  22. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  23. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  25. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  40. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  72. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  81. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  83. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  84. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  86. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_184c536d._.js → 2374f_147b1db0._.js} +1 -1
  88. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_be7e7ab5._.js → 2374f_36dd81aa._.js} +1 -1
  89. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4efa2a34._.js → 2374f_3eca1553._.js} +1 -1
  90. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d3aec1c3._.js → 2374f_4d1accac._.js} +1 -1
  91. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ced8bc96._.js → 2374f_4e2c9d06._.js} +2 -2
  92. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_75261269._.js → 2374f_b30bed04._.js} +1 -1
  93. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_84476e55._.js → 2374f_c99180da._.js} +1 -1
  94. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d7e5f4a0._.js → 2374f_f5998fca._.js} +1 -1
  95. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__a7aab2da._.js +4 -2
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/web_76ccf09f._.js +1 -1
  97. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  98. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  99. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  100. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  101. package/web/.next/standalone/web/.next/static/chunks/{6407c045dfc908fe.js → 1e9fdac31c7fd474.js} +3 -3
  102. package/web/.next/standalone/web/.next/static/chunks/{165f43d544d3a988.js → 6b8642759b62770c.js} +6 -4
  103. package/web/.next/standalone/web/.next/static/chunks/f81a19eca278424b.css +1 -0
  104. package/web/.next/{static/chunks/6407c045dfc908fe.js → standalone/web/.next/static/static/chunks/1e9fdac31c7fd474.js} +3 -3
  105. package/web/.next/standalone/web/.next/static/static/chunks/{165f43d544d3a988.js → 6b8642759b62770c.js} +6 -4
  106. package/web/.next/standalone/web/.next/static/static/chunks/f81a19eca278424b.css +1 -0
  107. package/web/.next/standalone/web/src/components/ai-elements/search-tool.tsx +107 -59
  108. package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +116 -172
  109. package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
  110. package/web/.next/standalone/web/src/lib/api.ts +33 -0
  111. package/web/.next/{standalone/web/.next/static/static/chunks/6407c045dfc908fe.js → static/chunks/1e9fdac31c7fd474.js} +3 -3
  112. package/web/.next/static/chunks/{165f43d544d3a988.js → 6b8642759b62770c.js} +6 -4
  113. package/web/.next/static/chunks/f81a19eca278424b.css +1 -0
  114. package/web/.next/standalone/web/.next/static/chunks/971d802e445eb147.css +0 -1
  115. package/web/.next/standalone/web/.next/static/static/chunks/971d802e445eb147.css +0 -1
  116. package/web/.next/static/chunks/971d802e445eb147.css +0 -1
  117. /package/web/.next/standalone/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_buildManifest.js +0 -0
  118. /package/web/.next/standalone/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_clientMiddlewareManifest.json +0 -0
  119. /package/web/.next/standalone/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_ssgManifest.js +0 -0
  120. /package/web/.next/standalone/web/.next/static/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_buildManifest.js +0 -0
  121. /package/web/.next/standalone/web/.next/static/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_clientMiddlewareManifest.json +0 -0
  122. /package/web/.next/standalone/web/.next/static/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_ssgManifest.js +0 -0
  123. /package/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_buildManifest.js +0 -0
  124. /package/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_clientMiddlewareManifest.json +0 -0
  125. /package/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_ssgManifest.js +0 -0
@@ -6,10 +6,7 @@ import {
6
6
  DialogContent,
7
7
  DialogHeader,
8
8
  DialogTitle,
9
- DialogDescription,
10
9
  } from "@/components/ui/dialog";
11
- import { ScrollArea } from "@/components/ui/scroll-area";
12
- import { Badge } from "@/components/ui/badge";
13
10
  import { cn } from "@/lib/utils";
14
11
  import {
15
12
  SearchIcon,
@@ -19,8 +16,7 @@ import {
19
16
  CheckCircleIcon,
20
17
  XCircleIcon,
21
18
  Loader2Icon,
22
- BotIcon,
23
- CodeIcon,
19
+ SparklesIcon,
24
20
  ChevronRightIcon,
25
21
  } from "lucide-react";
26
22
 
@@ -59,119 +55,108 @@ const getToolIcon = (toolName?: string) => {
59
55
  }
60
56
  };
61
57
 
58
+ /** Truncate text */
59
+ const truncate = (text: string, maxLen: number) => {
60
+ if (!text) return "";
61
+ return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
62
+ };
63
+
62
64
  export function SubagentModal({
63
65
  open,
64
66
  onOpenChange,
65
67
  title = "Explore Agent",
66
- description,
67
68
  steps,
68
69
  status,
69
70
  query,
70
71
  }: SubagentModalProps) {
71
- const scrollContainerRef = useRef<HTMLDivElement>(null);
72
-
73
- // Auto-scroll to bottom when new steps arrive
74
- useEffect(() => {
75
- if (scrollContainerRef.current && status === "running") {
76
- const container = scrollContainerRef.current;
77
- container.scrollTop = container.scrollHeight;
78
- }
79
- }, [steps, status]);
72
+ const scrollRef = useRef<HTMLDivElement>(null);
73
+ const bottomRef = useRef<HTMLDivElement>(null);
80
74
 
81
75
  const isRunning = status === "running";
82
76
  const isCompleted = status === "completed";
83
77
  const isError = status === "error";
84
78
 
79
+ // Auto-scroll to bottom when new steps arrive
80
+ useEffect(() => {
81
+ if (bottomRef.current && isRunning) {
82
+ bottomRef.current.scrollIntoView({ behavior: "smooth", block: "end" });
83
+ }
84
+ }, [steps, isRunning]);
85
+
85
86
  return (
86
87
  <Dialog open={open} onOpenChange={onOpenChange}>
87
- <DialogContent className="sm:max-w-2xl max-h-[85vh] flex flex-col gap-0 p-0 overflow-hidden">
88
+ <DialogContent className="sm:max-w-xl max-h-[80vh] flex flex-col gap-0 p-0 overflow-hidden">
88
89
  {/* Header */}
89
- <div className="px-6 py-4 border-b bg-gradient-to-r from-blue-500/5 to-purple-500/5">
90
- <DialogHeader>
91
- <div className="flex items-center gap-3">
92
- <div className={cn(
93
- "p-2 rounded-lg",
94
- isRunning && "bg-blue-500/10",
95
- isCompleted && "bg-green-500/10",
96
- isError && "bg-red-500/10"
97
- )}>
98
- {isRunning ? (
99
- <Loader2Icon className="size-5 text-blue-500 animate-spin" />
100
- ) : isCompleted ? (
101
- <CheckCircleIcon className="size-5 text-green-500" />
102
- ) : (
103
- <XCircleIcon className="size-5 text-red-500" />
104
- )}
105
- </div>
106
- <div>
107
- <DialogTitle className="flex items-center gap-2">
108
- <BotIcon className="size-4 text-muted-foreground" />
109
- {title}
110
- </DialogTitle>
111
- {description && (
112
- <DialogDescription className="mt-1">
113
- {description}
114
- </DialogDescription>
115
- )}
116
- </div>
117
- </div>
90
+ <div className="px-4 py-3 border-b flex items-center gap-3">
91
+ <div className="shrink-0">
92
+ {isRunning ? (
93
+ <Loader2Icon className="size-4 text-blue-500 animate-spin" />
94
+ ) : isCompleted ? (
95
+ <CheckCircleIcon className="size-4 text-green-500" />
96
+ ) : (
97
+ <XCircleIcon className="size-4 text-red-500" />
98
+ )}
99
+ </div>
100
+ <DialogHeader className="flex-1 space-y-0">
101
+ <DialogTitle className="text-sm font-medium">
102
+ {isRunning ? "SparkeCoder is exploring..." : isCompleted ? "Exploration complete" : "Exploration failed"}
103
+ </DialogTitle>
118
104
  </DialogHeader>
119
-
120
- {/* Query display */}
121
- {query && (
122
- <div className="mt-3 flex items-start gap-2 p-3 bg-muted/50 rounded-lg">
123
- <SearchIcon className="size-4 text-muted-foreground mt-0.5 shrink-0" />
124
- <p className="text-sm text-foreground">{query}</p>
125
- </div>
126
- )}
105
+ <span className="text-xs text-muted-foreground tabular-nums">
106
+ {steps.length} steps
107
+ </span>
127
108
  </div>
128
109
 
129
- {/* Steps content */}
130
- <div className="flex-1 overflow-hidden">
131
- <ScrollArea className="h-[50vh]">
132
- <div ref={scrollContainerRef} className="p-4 space-y-2">
133
- {steps.length === 0 ? (
134
- <div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
135
- <Loader2Icon className="size-8 animate-spin mb-3" />
136
- <p className="text-sm">Initializing explore agent...</p>
137
- </div>
138
- ) : (
139
- steps.map((step, index) => (
140
- <StepCard key={step.id || index} step={step} index={index} />
141
- ))
142
- )}
143
-
144
- {/* Streaming indicator */}
145
- {isRunning && steps.length > 0 && (
146
- <div className="flex items-center gap-2 py-2 text-muted-foreground">
147
- <Loader2Icon className="size-4 animate-spin" />
148
- <span className="text-xs">Searching...</span>
110
+ {/* Query */}
111
+ {query && (
112
+ <div className="px-4 py-2 border-b bg-muted/30">
113
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
114
+ <SearchIcon className="size-3 shrink-0" />
115
+ <span className="truncate" title={query}>{truncate(query, 80)}</span>
116
+ </div>
117
+ </div>
118
+ )}
119
+
120
+ {/* Steps - Terminal style log */}
121
+ <div
122
+ ref={scrollRef}
123
+ className="flex-1 overflow-auto bg-zinc-950 font-mono text-xs"
124
+ >
125
+ {steps.length === 0 ? (
126
+ <div className="flex items-center justify-center py-12 text-zinc-500">
127
+ <Loader2Icon className="size-4 animate-spin mr-2" />
128
+ Initializing...
129
+ </div>
130
+ ) : (
131
+ <div className="py-2">
132
+ {steps.map((step, index) => (
133
+ <LogLine key={step.id || index} step={step} />
134
+ ))}
135
+
136
+ {/* Auto-scroll anchor */}
137
+ <div ref={bottomRef} />
138
+
139
+ {/* Running indicator */}
140
+ {isRunning && (
141
+ <div className="px-3 py-1 flex items-center gap-2 text-zinc-500">
142
+ <Loader2Icon className="size-3 animate-spin" />
143
+ <span className="animate-pulse">_</span>
149
144
  </div>
150
145
  )}
151
146
  </div>
152
- </ScrollArea>
147
+ )}
153
148
  </div>
154
149
 
155
- {/* Footer with status */}
156
- <div className="px-6 py-3 border-t bg-muted/30 flex items-center justify-between">
157
- <div className="flex items-center gap-2">
158
- <Badge
159
- variant="secondary"
160
- className={cn(
161
- "text-xs",
162
- isRunning && "bg-blue-500/10 text-blue-600",
163
- isCompleted && "bg-green-500/10 text-green-600",
164
- isError && "bg-red-500/10 text-red-600"
165
- )}
166
- >
167
- {isRunning ? "Running" : isCompleted ? "Completed" : "Error"}
168
- </Badge>
169
- <span className="text-xs text-muted-foreground">
170
- {steps.length} step{steps.length !== 1 ? "s" : ""}
171
- </span>
172
- </div>
173
- <span className="text-xs text-muted-foreground">
174
- Powered by Gemini 2.0 Flash
150
+ {/* Footer */}
151
+ <div className="px-4 py-2 border-t bg-zinc-950 flex items-center gap-2">
152
+ <div className={cn(
153
+ "size-2 rounded-full",
154
+ isRunning && "bg-blue-500 animate-pulse",
155
+ isCompleted && "bg-green-500",
156
+ isError && "bg-red-500"
157
+ )} />
158
+ <span className="text-xs text-zinc-500">
159
+ {isRunning ? "Running" : isCompleted ? "Done" : "Failed"}
175
160
  </span>
176
161
  </div>
177
162
  </DialogContent>
@@ -179,97 +164,56 @@ export function SubagentModal({
179
164
  );
180
165
  }
181
166
 
182
- function StepCard({ step, index }: { step: SubagentStep; index: number }) {
167
+ /** Single log line in terminal style */
168
+ function LogLine({ step }: { step: SubagentStep }) {
183
169
  const ToolIcon = getToolIcon(step.toolName);
184
170
 
185
- return (
186
- <div className={cn(
187
- "rounded-lg border overflow-hidden",
188
- step.type === "tool_call" && "bg-blue-500/5 border-blue-500/20",
189
- step.type === "tool_result" && "bg-green-500/5 border-green-500/20",
190
- step.type === "text" && "bg-muted/50",
191
- step.type === "thought" && "bg-amber-500/5 border-amber-500/20"
192
- )}>
193
- <div className="px-3 py-2">
194
- <div className="flex items-start gap-2">
195
- {/* Step number */}
196
- <div className="flex items-center justify-center size-5 rounded-full bg-muted text-[10px] font-medium shrink-0 mt-0.5">
197
- {index + 1}
198
- </div>
199
-
200
- {/* Step content */}
201
- <div className="flex-1 min-w-0">
202
- {step.type === "tool_call" && (
203
- <div className="space-y-1">
204
- <div className="flex items-center gap-2">
205
- <ToolIcon className="size-4 text-blue-500" />
206
- <span className="font-medium text-sm text-blue-600">
207
- {step.toolName}
208
- </span>
209
- </div>
210
- {step.toolInput !== undefined && step.toolInput !== null && (
211
- <pre className="text-xs text-muted-foreground bg-background/50 rounded p-2 overflow-x-auto">
212
- {formatToolInput(step.toolInput)}
213
- </pre>
214
- )}
215
- </div>
216
- )}
217
-
218
- {step.type === "tool_result" && (
219
- <div className="space-y-1">
220
- <div className="flex items-center gap-2">
221
- <CheckCircleIcon className="size-4 text-green-500" />
222
- <span className="font-medium text-sm text-green-600">
223
- Result from {step.toolName}
224
- </span>
225
- </div>
226
- {step.toolOutput !== undefined && step.toolOutput !== null && (
227
- <pre className="text-xs text-muted-foreground bg-background/50 rounded p-2 overflow-x-auto max-h-[150px] overflow-y-auto">
228
- {formatToolOutput(step.toolOutput)}
229
- </pre>
230
- )}
231
- </div>
232
- )}
171
+ if (step.type === "tool_call") {
172
+ const inputStr = formatToolInput(step.toolInput);
173
+ return (
174
+ <div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
175
+ <ChevronRightIcon className="size-3 text-blue-400 mt-0.5 shrink-0" />
176
+ <ToolIcon className="size-3 text-blue-400 mt-0.5 shrink-0" />
177
+ <span className="text-blue-400">{step.toolName}</span>
178
+ {inputStr && (
179
+ <span className="text-zinc-600 truncate">{truncate(inputStr, 60)}</span>
180
+ )}
181
+ </div>
182
+ );
183
+ }
233
184
 
234
- {step.type === "text" && (
235
- <div className="flex items-start gap-2">
236
- <CodeIcon className="size-4 text-purple-500 mt-0.5 shrink-0" />
237
- <p className="text-sm">{step.content}</p>
238
- </div>
239
- )}
185
+ if (step.type === "tool_result") {
186
+ return (
187
+ <div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
188
+ <CheckCircleIcon className="size-3 text-green-500 mt-0.5 shrink-0" />
189
+ <span className="text-zinc-500 truncate">{truncate(step.content, 80)}</span>
190
+ </div>
191
+ );
192
+ }
240
193
 
241
- {step.type === "thought" && (
242
- <div className="flex items-start gap-2">
243
- <BotIcon className="size-4 text-amber-500 mt-0.5 shrink-0" />
244
- <p className="text-sm italic text-muted-foreground">{step.content}</p>
245
- </div>
246
- )}
247
- </div>
248
- </div>
194
+ if (step.type === "thought") {
195
+ return (
196
+ <div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
197
+ <SparklesIcon className="size-3 text-amber-400 mt-0.5 shrink-0" />
198
+ <span className="text-zinc-400 italic">{truncate(step.content, 80)}</span>
249
199
  </div>
200
+ );
201
+ }
202
+
203
+ // text type
204
+ return (
205
+ <div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
206
+ <span className="text-zinc-300">{truncate(step.content, 100)}</span>
250
207
  </div>
251
208
  );
252
209
  }
253
210
 
254
211
  function formatToolInput(input: unknown): string {
255
212
  try {
213
+ if (input === undefined || input === null) return "";
256
214
  if (typeof input === "string") return input;
257
- return JSON.stringify(input, null, 2);
215
+ return JSON.stringify(input);
258
216
  } catch {
259
217
  return String(input);
260
218
  }
261
219
  }
262
-
263
- function formatToolOutput(output: unknown): string {
264
- try {
265
- if (typeof output === "string") return output;
266
- const str = JSON.stringify(output, null, 2);
267
- // Truncate long outputs
268
- if (str.length > 1000) {
269
- return str.slice(0, 1000) + "\n... (truncated)";
270
- }
271
- return str;
272
- } catch {
273
- return String(output);
274
- }
275
- }
@@ -101,6 +101,8 @@ import {
101
101
  revertToCheckpoint,
102
102
  checkVersion,
103
103
  createSession,
104
+ getPendingInput,
105
+ getDevtoolsContext,
104
106
  type Session,
105
107
  type SSEEvent,
106
108
  type PendingApproval,
@@ -1486,10 +1488,73 @@ export function ChatInterface({ session }: ChatInterfaceProps) {
1486
1488
  };
1487
1489
  }, [session.id, isRunning]);
1488
1490
 
1489
- const handleSubmit = (promptText: string, attachments?: RunAgentAttachment[]) => {
1491
+ // Poll for pending input from devtools (when user selects a component externally)
1492
+ useEffect(() => {
1493
+ let isStale = false;
1494
+ const currentSessionId = session.id;
1495
+
1496
+ const checkForPendingInput = async () => {
1497
+ // Skip if we're running
1498
+ if (isRunning) return;
1499
+
1500
+ try {
1501
+ const pendingResult = await getPendingInput(currentSessionId);
1502
+
1503
+ // Check if stale or running after async
1504
+ if (isStale || isRunning) return;
1505
+
1506
+ if (pendingResult.hasPendingInput && pendingResult.text) {
1507
+ // Populate the input field with the pending text
1508
+ setInput((prev) => {
1509
+ // If there's already text, append with newline
1510
+ if (prev.trim()) {
1511
+ return prev + '\n\n' + pendingResult.text;
1512
+ }
1513
+ return pendingResult.text || '';
1514
+ });
1515
+
1516
+ // Focus the input field - find the textarea and focus it
1517
+ setTimeout(() => {
1518
+ const textarea = document.querySelector('textarea[placeholder*="Ask SparkECoder"]') as HTMLTextAreaElement | null;
1519
+ if (textarea) {
1520
+ textarea.focus();
1521
+ // Move cursor to end
1522
+ textarea.selectionStart = textarea.value.length;
1523
+ textarea.selectionEnd = textarea.value.length;
1524
+ }
1525
+ }, 100);
1526
+ }
1527
+ } catch (err) {
1528
+ // Ignore errors when polling
1529
+ }
1530
+ };
1531
+
1532
+ // Poll every 500ms for responsive feel
1533
+ const interval = setInterval(checkForPendingInput, 500);
1534
+
1535
+ return () => {
1536
+ isStale = true;
1537
+ clearInterval(interval);
1538
+ };
1539
+ }, [session.id, isRunning]);
1540
+
1541
+ const handleSubmit = async (promptText: string, attachments?: RunAgentAttachment[]) => {
1490
1542
  if (!promptText.trim() && (!attachments || attachments.length === 0)) return;
1491
1543
  if (isRunning) return;
1492
1544
 
1545
+ // Check if devtools is connected and get current page context
1546
+ let finalPrompt = promptText;
1547
+ try {
1548
+ const devtoolsInfo = await getDevtoolsContext(session.id);
1549
+ if (devtoolsInfo.connected && devtoolsInfo.context) {
1550
+ const ctx = devtoolsInfo.context;
1551
+ const contextPrefix = `[Sparkecode Devtools connected - User is currently viewing: ${ctx.pageName} (${ctx.path})]\n\n`;
1552
+ finalPrompt = contextPrefix + promptText;
1553
+ }
1554
+ } catch {
1555
+ // Ignore devtools context errors
1556
+ }
1557
+
1493
1558
  // Convert RunAgentAttachment to UserAttachment for display
1494
1559
  const userAttachments: UserAttachment[] | undefined = attachments?.map((a) => ({
1495
1560
  type: a.type,
@@ -1501,7 +1566,7 @@ export function ChatInterface({ session }: ChatInterfaceProps) {
1501
1566
  const userItem: ChatItem = {
1502
1567
  id: `user-${Date.now()}`,
1503
1568
  type: 'user-message',
1504
- content: promptText || '',
1569
+ content: promptText || '', // Show original prompt in UI (without context prefix)
1505
1570
  attachments: userAttachments,
1506
1571
  };
1507
1572
 
@@ -1518,7 +1583,7 @@ export function ChatInterface({ session }: ChatInterfaceProps) {
1518
1583
  currentReasoningRef.current = '';
1519
1584
  toolCallsRef.current = [];
1520
1585
 
1521
- const cancel = runAgent(session.id, promptText || 'Please analyze the attached files.', handleSSEEvent, {
1586
+ const cancel = runAgent(session.id, finalPrompt || 'Please analyze the attached files.', handleSSEEvent, {
1522
1587
  onStreamId: (id) => setCurrentStreamId(id),
1523
1588
  attachments,
1524
1589
  });
@@ -157,6 +157,39 @@ export async function getSessionTodos(sessionId: string): Promise<TodosResponse>
157
157
  return res.json();
158
158
  }
159
159
 
160
+ export interface PendingInputResponse {
161
+ hasPendingInput: boolean;
162
+ text: string | null;
163
+ createdAt?: string;
164
+ }
165
+
166
+ // Get pending input for a session (from devtools, etc.)
167
+ // This clears the pending input after reading
168
+ export async function getPendingInput(sessionId: string): Promise<PendingInputResponse> {
169
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/pending-input`);
170
+ return res.json();
171
+ }
172
+
173
+ export interface DevtoolsContextResponse {
174
+ connected: boolean;
175
+ context: {
176
+ url: string;
177
+ path: string;
178
+ pageName: string;
179
+ lastHeartbeat: string;
180
+ } | null;
181
+ }
182
+
183
+ // Get devtools context for a session (current page user is viewing)
184
+ export async function getDevtoolsContext(sessionId: string): Promise<DevtoolsContextResponse> {
185
+ try {
186
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/devtools-context`);
187
+ return res.json();
188
+ } catch {
189
+ return { connected: false, context: null };
190
+ }
191
+ }
192
+
160
193
  export async function updateSession(id: string, updates: { model?: string; name?: string; toolApprovals?: Record<string, boolean> }): Promise<Session> {
161
194
  const res = await fetch(`${getApiBase()}/sessions/${id}`, {
162
195
  method: 'PATCH',