@kirosnn/mosaic 0.0.91 → 0.73.0

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 (99) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -6
  3. package/package.json +55 -48
  4. package/src/agent/Agent.ts +353 -131
  5. package/src/agent/context.ts +4 -4
  6. package/src/agent/prompts/systemPrompt.ts +209 -70
  7. package/src/agent/prompts/toolsPrompt.ts +285 -138
  8. package/src/agent/provider/anthropic.ts +109 -105
  9. package/src/agent/provider/google.ts +111 -107
  10. package/src/agent/provider/mistral.ts +95 -95
  11. package/src/agent/provider/ollama.ts +73 -17
  12. package/src/agent/provider/openai.ts +146 -102
  13. package/src/agent/provider/rateLimit.ts +178 -0
  14. package/src/agent/provider/reasoning.ts +29 -0
  15. package/src/agent/provider/xai.ts +108 -104
  16. package/src/agent/tools/definitions.ts +15 -1
  17. package/src/agent/tools/executor.ts +717 -98
  18. package/src/agent/tools/exploreExecutor.ts +20 -22
  19. package/src/agent/tools/fetch.ts +58 -0
  20. package/src/agent/tools/glob.ts +20 -4
  21. package/src/agent/tools/grep.ts +64 -9
  22. package/src/agent/tools/plan.ts +27 -0
  23. package/src/agent/tools/question.ts +7 -1
  24. package/src/agent/tools/read.ts +2 -0
  25. package/src/agent/types.ts +15 -14
  26. package/src/components/App.tsx +50 -8
  27. package/src/components/CustomInput.tsx +461 -77
  28. package/src/components/Main.tsx +1459 -1112
  29. package/src/components/Setup.tsx +1 -1
  30. package/src/components/ShortcutsModal.tsx +11 -8
  31. package/src/components/Welcome.tsx +1 -1
  32. package/src/components/main/ApprovalPanel.tsx +4 -3
  33. package/src/components/main/ChatPage.tsx +858 -516
  34. package/src/components/main/HomePage.tsx +58 -39
  35. package/src/components/main/QuestionPanel.tsx +52 -7
  36. package/src/components/main/ThinkingIndicator.tsx +13 -2
  37. package/src/components/main/types.ts +11 -10
  38. package/src/index.tsx +53 -25
  39. package/src/mcp/approvalPolicy.ts +148 -0
  40. package/src/mcp/cli/add.ts +185 -0
  41. package/src/mcp/cli/doctor.ts +77 -0
  42. package/src/mcp/cli/index.ts +85 -0
  43. package/src/mcp/cli/list.ts +50 -0
  44. package/src/mcp/cli/logs.ts +24 -0
  45. package/src/mcp/cli/manage.ts +99 -0
  46. package/src/mcp/cli/show.ts +53 -0
  47. package/src/mcp/cli/tools.ts +77 -0
  48. package/src/mcp/config.ts +223 -0
  49. package/src/mcp/index.ts +80 -0
  50. package/src/mcp/processManager.ts +299 -0
  51. package/src/mcp/rateLimiter.ts +50 -0
  52. package/src/mcp/registry.ts +151 -0
  53. package/src/mcp/schemaConverter.ts +100 -0
  54. package/src/mcp/servers/navigation.ts +854 -0
  55. package/src/mcp/toolCatalog.ts +169 -0
  56. package/src/mcp/types.ts +95 -0
  57. package/src/utils/approvalBridge.ts +45 -12
  58. package/src/utils/approvalModeBridge.ts +17 -0
  59. package/src/utils/commands/approvals.ts +48 -0
  60. package/src/utils/commands/compact.ts +30 -0
  61. package/src/utils/commands/echo.ts +1 -1
  62. package/src/utils/commands/image.ts +109 -0
  63. package/src/utils/commands/index.ts +9 -7
  64. package/src/utils/commands/new.ts +15 -0
  65. package/src/utils/commands/types.ts +3 -0
  66. package/src/utils/config.ts +3 -1
  67. package/src/utils/diffRendering.tsx +13 -16
  68. package/src/utils/exploreBridge.ts +10 -0
  69. package/src/utils/history.ts +82 -40
  70. package/src/utils/imageBridge.ts +28 -0
  71. package/src/utils/images.ts +31 -0
  72. package/src/utils/markdown.tsx +163 -99
  73. package/src/utils/models.ts +31 -16
  74. package/src/utils/notificationBridge.ts +23 -0
  75. package/src/utils/questionBridge.ts +36 -1
  76. package/src/utils/tokenEstimator.ts +32 -0
  77. package/src/utils/toolFormatting.ts +428 -48
  78. package/src/web/app.tsx +65 -5
  79. package/src/web/assets/css/ChatPage.css +102 -30
  80. package/src/web/assets/css/MessageItem.css +26 -29
  81. package/src/web/assets/css/ThinkingIndicator.css +44 -6
  82. package/src/web/assets/css/ToolMessage.css +36 -14
  83. package/src/web/components/ChatPage.tsx +228 -105
  84. package/src/web/components/HomePage.tsx +3 -3
  85. package/src/web/components/MessageItem.tsx +80 -81
  86. package/src/web/components/QuestionPanel.tsx +72 -12
  87. package/src/web/components/Setup.tsx +1 -1
  88. package/src/web/components/Sidebar.tsx +1 -3
  89. package/src/web/components/ThinkingIndicator.tsx +41 -21
  90. package/src/web/router.ts +1 -1
  91. package/src/web/server.tsx +894 -662
  92. package/src/web/storage.ts +23 -1
  93. package/src/web/types.ts +7 -6
  94. package/src/utils/commands/redo.ts +0 -74
  95. package/src/utils/commands/sessions.ts +0 -129
  96. package/src/utils/commands/undo.ts +0 -75
  97. package/src/utils/undoRedo.ts +0 -429
  98. package/src/utils/undoRedoBridge.ts +0 -45
  99. package/src/utils/undoRedoDb.ts +0 -338
@@ -10,14 +10,41 @@ interface QuestionPanelProps {
10
10
  export function QuestionPanel({ request, onAnswer }: QuestionPanelProps) {
11
11
  const [selectedIndex, setSelectedIndex] = useState(0);
12
12
  const [customText, setCustomText] = useState('');
13
+ const [remaining, setRemaining] = useState<number | null>(request.timeout ?? null);
14
+ const [validationError, setValidationError] = useState<string | null>(null);
13
15
  const inputRef = useRef<HTMLInputElement>(null);
14
16
 
15
17
  useEffect(() => {
16
18
  setSelectedIndex(0);
17
19
  setCustomText('');
20
+ setValidationError(null);
21
+ setRemaining(request.timeout ?? null);
18
22
  if (inputRef.current) inputRef.current.focus();
19
23
  }, [request.id]);
20
24
 
25
+ useEffect(() => {
26
+ if (remaining === null || remaining <= 0) return;
27
+ const id = setInterval(() => {
28
+ setRemaining(prev => (prev !== null && prev > 0 ? prev - 1 : prev));
29
+ }, 1000);
30
+ return () => clearInterval(id);
31
+ }, [remaining !== null]);
32
+
33
+ const validateCustomText = (text: string): boolean => {
34
+ if (!request.validation) return true;
35
+ try {
36
+ if (!new RegExp(request.validation.pattern).test(text)) {
37
+ setValidationError(request.validation.message || `Input must match: ${request.validation.pattern}`);
38
+ return false;
39
+ }
40
+ } catch {
41
+ setValidationError('Invalid validation pattern');
42
+ return false;
43
+ }
44
+ setValidationError(null);
45
+ return true;
46
+ };
47
+
21
48
  const handleKeyDown = (e: React.KeyboardEvent) => {
22
49
  if (e.key === 'ArrowUp') {
23
50
  e.preventDefault();
@@ -28,7 +55,9 @@ export function QuestionPanel({ request, onAnswer }: QuestionPanelProps) {
28
55
  } else if (e.key === 'Enter') {
29
56
  e.preventDefault();
30
57
  if (customText.trim()) {
31
- onAnswer(0, customText);
58
+ if (validateCustomText(customText)) {
59
+ onAnswer(0, customText);
60
+ }
32
61
  } else {
33
62
  onAnswer(selectedIndex);
34
63
  }
@@ -46,15 +75,22 @@ export function QuestionPanel({ request, onAnswer }: QuestionPanelProps) {
46
75
 
47
76
  const handleSubmitCustom = (e: React.FormEvent) => {
48
77
  e.preventDefault();
49
- if (customText.trim()) {
78
+ if (customText.trim() && validateCustomText(customText)) {
50
79
  onAnswer(0, customText);
51
80
  }
52
81
  };
53
82
 
83
+ let lastGroup: string | undefined;
84
+
54
85
  return (
55
86
  <div className="panel question-panel">
56
87
  <div className="panel-header">
57
88
  <strong>Question</strong>
89
+ {remaining !== null && (
90
+ <span style={{ marginLeft: '1rem', color: remaining <= 5 ? '#ff4444' : 'var(--text-muted)' }}>
91
+ Timeout: {remaining}s
92
+ </span>
93
+ )}
58
94
  </div>
59
95
  <div className="panel-content">
60
96
  <div className="question-prompt">
@@ -63,17 +99,28 @@ export function QuestionPanel({ request, onAnswer }: QuestionPanelProps) {
63
99
  ))}
64
100
  </div>
65
101
  <div className="question-options">
66
- {request.options.map((option, index) => (
67
- <div
68
- key={index}
69
- className={`question-option ${index === selectedIndex ? 'selected' : ''}`}
70
- onClick={() => handleOptionClick(index)}
71
- >
72
- <span className="option-key">{index + 1}.</span>
73
- <span className="option-label">{option.label}</span>
74
- </div>
75
- ))}
102
+ {request.options.map((option, index) => {
103
+ const showGroupHeader = option.group && option.group !== lastGroup;
104
+ lastGroup = option.group;
105
+ return (
106
+ <React.Fragment key={index}>
107
+ {showGroupHeader && (
108
+ <div className="option-group-header">{option.group}</div>
109
+ )}
110
+ <div
111
+ className={`question-option ${index === selectedIndex ? 'selected' : ''}`}
112
+ onClick={() => handleOptionClick(index)}
113
+ >
114
+ <span className="option-key">{index + 1}.</span>
115
+ <span className="option-label">{option.label}</span>
116
+ </div>
117
+ </React.Fragment>
118
+ );
119
+ })}
76
120
  </div>
121
+ {validationError && (
122
+ <div className="validation-error">{validationError}</div>
123
+ )}
77
124
  <div className="custom-input-container">
78
125
  <form onSubmit={handleSubmitCustom}>
79
126
  <input
@@ -111,6 +158,13 @@ export function QuestionPanel({ request, onAnswer }: QuestionPanelProps) {
111
158
  gap: 0.5rem;
112
159
  margin-bottom: 1rem;
113
160
  }
161
+ .option-group-header {
162
+ font-size: 0.85rem;
163
+ color: var(--text-muted);
164
+ font-weight: 600;
165
+ margin-top: 0.5rem;
166
+ padding-left: 0.25rem;
167
+ }
114
168
  .question-option {
115
169
  padding: 0.5rem;
116
170
  border-radius: 4px;
@@ -130,6 +184,12 @@ export function QuestionPanel({ request, onAnswer }: QuestionPanelProps) {
130
184
  color: var(--text-muted);
131
185
  width: 1.5rem;
132
186
  }
187
+ .validation-error {
188
+ color: #ff4444;
189
+ font-size: 0.85rem;
190
+ margin-bottom: 0.5rem;
191
+ padding-left: 0.25rem;
192
+ }
133
193
  .panel-input {
134
194
  width: 100%;
135
195
  padding: 0.75rem;
@@ -208,4 +208,4 @@ export function Setup({ onComplete }: SetupProps) {
208
208
  </div>
209
209
  </div>
210
210
  );
211
- }
211
+ }
@@ -12,8 +12,6 @@ interface GroupedConversations {
12
12
 
13
13
  function getTimePeriod(timestamp: number): TimePeriod {
14
14
  const now = new Date();
15
- const date = new Date(timestamp);
16
-
17
15
  const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
18
16
  const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000;
19
17
  const startOf7DaysAgo = startOfToday - 7 * 24 * 60 * 60 * 1000;
@@ -289,4 +287,4 @@ export function Sidebar({
289
287
  )}
290
288
  </>
291
289
  );
292
- }
290
+ }
@@ -19,6 +19,8 @@ const THINKING_WORDS = [
19
19
  interface ThinkingIndicatorProps {
20
20
  startTime?: number;
21
21
  tokens?: number;
22
+ inProgressStep?: string;
23
+ nextStep?: string;
22
24
  }
23
25
 
24
26
  function formatElapsedTime(startTime: number | undefined): string {
@@ -36,7 +38,7 @@ function formatElapsedTime(startTime: number | undefined): string {
36
38
  return `${seconds}s`;
37
39
  }
38
40
 
39
- export function ThinkingIndicator({ startTime, tokens }: ThinkingIndicatorProps) {
41
+ export function ThinkingIndicator({ startTime, tokens, nextStep }: ThinkingIndicatorProps) {
40
42
  const [shimmerPos, setShimmerPos] = useState(-2);
41
43
  const [, setTick] = useState(0);
42
44
  const thinkingWord = useMemo(
@@ -60,26 +62,44 @@ export function ThinkingIndicator({ startTime, tokens }: ThinkingIndicatorProps)
60
62
  const elapsedStr = formatElapsedTime(startTime);
61
63
 
62
64
  return (
63
- <div className="thinking-indicator">
64
- <span className="thinking-icon">&#x2058;</span>
65
- <span className="thinking-text">
66
- {text.split("").map((char, index) => {
67
- const inShimmer = index === shimmerPos || index === shimmerPos - 1;
68
- return (
69
- <span
70
- key={index}
71
- className={inShimmer ? "shimmer-active" : "shimmer-dim"}
72
- >
73
- {char}
74
- </span>
75
- );
76
- })}
77
- </span>
78
- {elapsedStr && <span className="thinking-elapsed"> - {elapsedStr}</span>}
79
- <span className="thinking-hint"> - esc to cancel</span>
80
- {tokens !== undefined && tokens > 0 && (
81
- <span className="thinking-tokens"> - {tokens.toLocaleString()} tokens</span>
65
+ <div className="thinking-block">
66
+ <div className="thinking-indicator">
67
+ <span className="thinking-icon">&#x2058;</span>
68
+ <span className="thinking-text">
69
+ {text.split("").map((char, index) => {
70
+ const inShimmer = index === shimmerPos || index === shimmerPos - 1;
71
+ return (
72
+ <span
73
+ key={index}
74
+ className={inShimmer ? "shimmer-active" : "shimmer-dim"}
75
+ >
76
+ {char}
77
+ </span>
78
+ );
79
+ })}
80
+ </span>
81
+ {elapsedStr && (
82
+ <>
83
+ <span className="thinking-sep"> </span>
84
+ <span className="thinking-elapsed">{elapsedStr}</span>
85
+ </>
86
+ )}
87
+ <span className="thinking-sep"> — </span>
88
+ <span className="thinking-hint">esc to cancel</span>
89
+ {tokens !== undefined && tokens > 0 && (
90
+ <>
91
+ <span className="thinking-sep"> — </span>
92
+ <span className="thinking-tokens">{tokens.toLocaleString()} tokens</span>
93
+ </>
94
+ )}
95
+ </div>
96
+ {nextStep && (
97
+ <div className="thinking-next-line">
98
+ <span className="thinking-next-icon">⎿ </span>
99
+ <span className="thinking-next-label">Next:</span>
100
+ <span className="thinking-next-step">{nextStep}</span>
101
+ </div>
82
102
  )}
83
103
  </div>
84
104
  );
85
- }
105
+ }
package/src/web/router.ts CHANGED
@@ -43,4 +43,4 @@ export function navigateTo(route: Route): void {
43
43
  export function replaceTo(route: Route): void {
44
44
  const path = buildPath(route);
45
45
  window.history.replaceState(null, '', path);
46
- }
46
+ }