groove-dev 0.27.14 → 0.27.17
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.
- package/README.md +37 -1
- package/developerID_application.cer +0 -0
- package/node_modules/@groove-dev/daemon/src/api.js +587 -68
- package/node_modules/@groove-dev/daemon/src/classifier.js +24 -0
- package/node_modules/@groove-dev/daemon/src/credentials.js +12 -2
- package/node_modules/@groove-dev/daemon/src/federation/ambassador.js +204 -0
- package/node_modules/@groove-dev/daemon/src/federation/connection.js +359 -0
- package/node_modules/@groove-dev/daemon/src/federation/contracts.js +112 -0
- package/node_modules/@groove-dev/daemon/src/federation/whitelist.js +190 -0
- package/node_modules/@groove-dev/daemon/src/federation.js +166 -7
- package/node_modules/@groove-dev/daemon/src/index.js +172 -19
- package/node_modules/@groove-dev/daemon/src/introducer.js +52 -7
- package/node_modules/@groove-dev/daemon/src/journalist.js +46 -1
- package/node_modules/@groove-dev/daemon/src/memory.js +36 -16
- package/node_modules/@groove-dev/daemon/src/process.js +140 -23
- package/node_modules/@groove-dev/daemon/src/providers/base.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +124 -28
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +104 -17
- package/node_modules/@groove-dev/daemon/src/providers/index.js +17 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +10 -1
- package/node_modules/@groove-dev/daemon/src/rotator.js +93 -30
- package/node_modules/@groove-dev/daemon/src/skills.js +33 -3
- package/node_modules/@groove-dev/daemon/src/terminal-pty.js +9 -1
- package/node_modules/@groove-dev/daemon/src/tool-executor.js +11 -5
- package/node_modules/@groove-dev/daemon/src/toys.js +69 -0
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +24 -5
- package/node_modules/@groove-dev/daemon/templates/toys-catalog.json +242 -0
- package/node_modules/@groove-dev/daemon/test/classifier.test.js +98 -0
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +72 -1
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +117 -0
- package/node_modules/@groove-dev/daemon/test/memory.test.js +37 -1
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +183 -4
- package/node_modules/@groove-dev/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -2
- package/node_modules/@groove-dev/gui/index.html +1 -0
- package/node_modules/@groove-dev/gui/src/app.css +7 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +37 -10
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +21 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +11 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/node_modules/@groove-dev/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +33 -2
- package/node_modules/@groove-dev/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/node_modules/@groove-dev/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/node_modules/@groove-dev/gui/src/components/editor/goto-line.jsx +35 -0
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +12 -6
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +13 -3
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +6 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/node_modules/@groove-dev/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +12 -5
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-activity.jsx +98 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-panel.jsx +290 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-peers.jsx +126 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +110 -67
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/settings/server-detail.jsx +310 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +4 -1
- package/node_modules/@groove-dev/gui/src/components/settings/server-list.jsx +59 -0
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +78 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +144 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/lib/electron.js +15 -0
- package/node_modules/@groove-dev/gui/src/lib/format.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +373 -58
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +148 -42
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +92 -2
- package/node_modules/@groove-dev/gui/src/views/federation.jsx +37 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +2 -42
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +32 -132
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +327 -0
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +162 -0
- package/package.json +1 -1
- package/packages/daemon/src/api.js +587 -68
- package/packages/daemon/src/classifier.js +24 -0
- package/packages/daemon/src/credentials.js +12 -2
- package/packages/daemon/src/federation/ambassador.js +204 -0
- package/packages/daemon/src/federation/connection.js +359 -0
- package/packages/daemon/src/federation/contracts.js +112 -0
- package/packages/daemon/src/federation/whitelist.js +190 -0
- package/packages/daemon/src/federation.js +166 -7
- package/packages/daemon/src/index.js +172 -19
- package/packages/daemon/src/introducer.js +52 -7
- package/packages/daemon/src/journalist.js +46 -1
- package/packages/daemon/src/memory.js +36 -16
- package/packages/daemon/src/process.js +140 -23
- package/packages/daemon/src/providers/base.js +1 -0
- package/packages/daemon/src/providers/claude-code.js +1 -0
- package/packages/daemon/src/providers/codex.js +124 -28
- package/packages/daemon/src/providers/gemini.js +104 -17
- package/packages/daemon/src/providers/index.js +17 -0
- package/packages/daemon/src/registry.js +10 -1
- package/packages/daemon/src/rotator.js +93 -30
- package/packages/daemon/src/skills.js +33 -3
- package/packages/daemon/src/terminal-pty.js +9 -1
- package/packages/daemon/src/tool-executor.js +11 -5
- package/packages/daemon/src/toys.js +69 -0
- package/packages/daemon/src/tunnel-manager.js +24 -5
- package/packages/daemon/templates/toys-catalog.json +242 -0
- package/packages/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/packages/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/packages/gui/dist/index.html +3 -2
- package/packages/gui/index.html +1 -0
- package/packages/gui/src/app.css +7 -0
- package/packages/gui/src/app.jsx +37 -10
- package/packages/gui/src/components/agents/agent-chat.jsx +21 -31
- package/packages/gui/src/components/agents/agent-config.jsx +11 -6
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/packages/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/packages/gui/src/components/editor/code-editor.jsx +33 -2
- package/packages/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/packages/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/packages/gui/src/components/editor/goto-line.jsx +35 -0
- package/packages/gui/src/components/editor/terminal.jsx +12 -6
- package/packages/gui/src/components/layout/activity-bar.jsx +13 -3
- package/packages/gui/src/components/layout/app-shell.jsx +0 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/packages/gui/src/components/layout/command-palette.jsx +6 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/packages/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/packages/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/packages/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +12 -5
- package/packages/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/packages/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/packages/gui/src/components/settings/federation-activity.jsx +98 -0
- package/packages/gui/src/components/settings/federation-panel.jsx +290 -0
- package/packages/gui/src/components/settings/federation-peers.jsx +126 -0
- package/packages/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +110 -67
- package/packages/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/packages/gui/src/components/settings/server-detail.jsx +310 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +4 -1
- package/packages/gui/src/components/settings/server-list.jsx +59 -0
- package/packages/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/packages/gui/src/components/toys/toy-card.jsx +78 -0
- package/packages/gui/src/components/toys/toy-creator.jsx +144 -0
- package/packages/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/packages/gui/src/components/ui/toast.jsx +2 -2
- package/packages/gui/src/lib/electron.js +15 -0
- package/packages/gui/src/lib/format.js +1 -0
- package/packages/gui/src/stores/groove.js +373 -58
- package/packages/gui/src/views/agents.jsx +148 -42
- package/packages/gui/src/views/editor.jsx +92 -2
- package/packages/gui/src/views/federation.jsx +37 -0
- package/packages/gui/src/views/marketplace.jsx +2 -42
- package/packages/gui/src/views/settings.jsx +32 -132
- package/packages/gui/src/views/subscription-panel.jsx +327 -0
- package/packages/gui/src/views/teams.jsx +3 -3
- package/packages/gui/src/views/toys.jsx +162 -0
- package/plans/chat-persistence-refactor.md +154 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +0 -677
- package/packages/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/packages/gui/dist/assets/index-zdzOLAZM.js +0 -677
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "nasa-eonet",
|
|
4
|
+
"name": "NASA Earth Observatory",
|
|
5
|
+
"description": "Track natural events on Earth in real-time — wildfires, storms, volcanic eruptions, and more from NASA's EONET feed.",
|
|
6
|
+
"category": "space",
|
|
7
|
+
"icon": "Globe",
|
|
8
|
+
"docsUrl": "https://eonet.gsfc.nasa.gov/docs/v3",
|
|
9
|
+
"baseUrl": "https://eonet.gsfc.nasa.gov/api/v3",
|
|
10
|
+
"authType": "apiKey",
|
|
11
|
+
"keyHeader": "api_key",
|
|
12
|
+
"sampleEndpoints": [
|
|
13
|
+
"GET /events",
|
|
14
|
+
"GET /events?status=open",
|
|
15
|
+
"GET /categories",
|
|
16
|
+
"GET /events?category=wildfires"
|
|
17
|
+
],
|
|
18
|
+
"starterPrompts": [
|
|
19
|
+
"Build a live wildfire tracker with map visualization",
|
|
20
|
+
"Create a natural disaster dashboard with event timelines",
|
|
21
|
+
"Build an alert system for volcanic eruptions near populated areas",
|
|
22
|
+
"Visualize storm patterns across the globe over the last 30 days"
|
|
23
|
+
],
|
|
24
|
+
"difficulty": "intermediate"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "nasa-apod",
|
|
28
|
+
"name": "NASA Astronomy Picture of the Day",
|
|
29
|
+
"description": "Access NASA's iconic daily astronomy images and explanations — one stunning space photo every day since 1995.",
|
|
30
|
+
"category": "space",
|
|
31
|
+
"icon": "Globe",
|
|
32
|
+
"docsUrl": "https://api.nasa.gov",
|
|
33
|
+
"baseUrl": "https://api.nasa.gov/planetary",
|
|
34
|
+
"authType": "apiKey",
|
|
35
|
+
"keyHeader": "api_key",
|
|
36
|
+
"sampleEndpoints": [
|
|
37
|
+
"GET /apod?api_key=DEMO_KEY",
|
|
38
|
+
"GET /apod?date=2024-01-01",
|
|
39
|
+
"GET /apod?start_date=2024-01-01&end_date=2024-01-07",
|
|
40
|
+
"GET /apod?count=5"
|
|
41
|
+
],
|
|
42
|
+
"starterPrompts": [
|
|
43
|
+
"Build a space photo gallery with daily updates",
|
|
44
|
+
"Create a screensaver app that cycles through APOD images",
|
|
45
|
+
"Build a quiz game using astronomy photo descriptions",
|
|
46
|
+
"Make a calendar app with a space photo for each day"
|
|
47
|
+
],
|
|
48
|
+
"difficulty": "beginner"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "openweathermap",
|
|
52
|
+
"name": "OpenWeatherMap",
|
|
53
|
+
"description": "Get current weather, forecasts, and historical data for any location on Earth with this popular weather API.",
|
|
54
|
+
"category": "weather",
|
|
55
|
+
"icon": "Cloud",
|
|
56
|
+
"docsUrl": "https://openweathermap.org/api",
|
|
57
|
+
"baseUrl": "https://api.openweathermap.org/data/2.5",
|
|
58
|
+
"authType": "apiKey",
|
|
59
|
+
"keyHeader": "appid",
|
|
60
|
+
"sampleEndpoints": [
|
|
61
|
+
"GET /weather?q=London&appid=KEY",
|
|
62
|
+
"GET /forecast?q=Tokyo&appid=KEY",
|
|
63
|
+
"GET /weather?lat=40.7&lon=-74.0&appid=KEY",
|
|
64
|
+
"GET /air_pollution?lat=50&lon=50&appid=KEY"
|
|
65
|
+
],
|
|
66
|
+
"starterPrompts": [
|
|
67
|
+
"Build a weather dashboard with 5-day forecasts",
|
|
68
|
+
"Create a travel planner that suggests destinations by weather",
|
|
69
|
+
"Build an air quality monitor for major cities",
|
|
70
|
+
"Make a weather comparison tool for two cities side by side"
|
|
71
|
+
],
|
|
72
|
+
"difficulty": "beginner"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "coingecko",
|
|
76
|
+
"name": "CoinGecko",
|
|
77
|
+
"description": "Free crypto market data — prices, market caps, volumes, and trending coins across thousands of cryptocurrencies.",
|
|
78
|
+
"category": "finance",
|
|
79
|
+
"icon": "Coins",
|
|
80
|
+
"docsUrl": "https://docs.coingecko.com/v3",
|
|
81
|
+
"baseUrl": "https://api.coingecko.com/api/v3",
|
|
82
|
+
"authType": "none",
|
|
83
|
+
"keyHeader": null,
|
|
84
|
+
"sampleEndpoints": [
|
|
85
|
+
"GET /simple/price?ids=bitcoin&vs_currencies=usd",
|
|
86
|
+
"GET /coins/markets?vs_currency=usd&order=market_cap_desc",
|
|
87
|
+
"GET /search/trending",
|
|
88
|
+
"GET /coins/bitcoin/market_chart?vs_currency=usd&days=30"
|
|
89
|
+
],
|
|
90
|
+
"starterPrompts": [
|
|
91
|
+
"Build a crypto portfolio tracker with live prices",
|
|
92
|
+
"Create a trending coins dashboard with price charts",
|
|
93
|
+
"Build a price alert system for your favorite coins",
|
|
94
|
+
"Make a crypto market overview with top gainers and losers"
|
|
95
|
+
],
|
|
96
|
+
"difficulty": "beginner"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "alpha-vantage",
|
|
100
|
+
"name": "Alpha Vantage",
|
|
101
|
+
"description": "Stock market data, forex, and crypto through a powerful financial API — real-time and historical time series.",
|
|
102
|
+
"category": "finance",
|
|
103
|
+
"icon": "Coins",
|
|
104
|
+
"docsUrl": "https://www.alphavantage.co/documentation",
|
|
105
|
+
"baseUrl": "https://www.alphavantage.co",
|
|
106
|
+
"authType": "apiKey",
|
|
107
|
+
"keyHeader": "apikey",
|
|
108
|
+
"sampleEndpoints": [
|
|
109
|
+
"GET /query?function=TIME_SERIES_DAILY&symbol=AAPL&apikey=KEY",
|
|
110
|
+
"GET /query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=KEY",
|
|
111
|
+
"GET /query?function=SYMBOL_SEARCH&keywords=tesla&apikey=KEY",
|
|
112
|
+
"GET /query?function=NEWS_SENTIMENT&tickers=AAPL&apikey=KEY"
|
|
113
|
+
],
|
|
114
|
+
"starterPrompts": [
|
|
115
|
+
"Build a stock screener with technical indicators",
|
|
116
|
+
"Create a portfolio analyzer with historical performance charts",
|
|
117
|
+
"Build a stock news sentiment tracker",
|
|
118
|
+
"Make a comparison tool for multiple stocks over time"
|
|
119
|
+
],
|
|
120
|
+
"difficulty": "intermediate"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "pokeapi",
|
|
124
|
+
"name": "PokeAPI",
|
|
125
|
+
"description": "The complete Pokemon encyclopedia API — every Pokemon, move, ability, and item from all generations.",
|
|
126
|
+
"category": "fun",
|
|
127
|
+
"icon": "Gamepad2",
|
|
128
|
+
"docsUrl": "https://pokeapi.co/docs/v2",
|
|
129
|
+
"baseUrl": "https://pokeapi.co/api/v2",
|
|
130
|
+
"authType": "none",
|
|
131
|
+
"keyHeader": null,
|
|
132
|
+
"sampleEndpoints": [
|
|
133
|
+
"GET /pokemon/pikachu",
|
|
134
|
+
"GET /pokemon?limit=20&offset=0",
|
|
135
|
+
"GET /type/fire",
|
|
136
|
+
"GET /ability/overgrow"
|
|
137
|
+
],
|
|
138
|
+
"starterPrompts": [
|
|
139
|
+
"Build a Pokedex with search and type filtering",
|
|
140
|
+
"Create a Pokemon battle simulator",
|
|
141
|
+
"Build a team builder that suggests balanced teams",
|
|
142
|
+
"Make a Pokemon quiz game with sprites and stats"
|
|
143
|
+
],
|
|
144
|
+
"difficulty": "beginner"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "open-trivia-db",
|
|
148
|
+
"name": "Open Trivia DB",
|
|
149
|
+
"description": "Free trivia question database with thousands of questions across dozens of categories and difficulty levels.",
|
|
150
|
+
"category": "fun",
|
|
151
|
+
"icon": "Gamepad2",
|
|
152
|
+
"docsUrl": "https://opentdb.com/api_config.php",
|
|
153
|
+
"baseUrl": "https://opentdb.com",
|
|
154
|
+
"authType": "none",
|
|
155
|
+
"keyHeader": null,
|
|
156
|
+
"sampleEndpoints": [
|
|
157
|
+
"GET /api.php?amount=10",
|
|
158
|
+
"GET /api.php?amount=10&category=9&difficulty=easy",
|
|
159
|
+
"GET /api_category.php",
|
|
160
|
+
"GET /api.php?amount=10&type=multiple"
|
|
161
|
+
],
|
|
162
|
+
"starterPrompts": [
|
|
163
|
+
"Build a multiplayer trivia game with scoreboards",
|
|
164
|
+
"Create a daily trivia challenge with streaks",
|
|
165
|
+
"Build a study tool that quizzes you on specific topics",
|
|
166
|
+
"Make a trivia night host app with rounds and scoring"
|
|
167
|
+
],
|
|
168
|
+
"difficulty": "beginner"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "nominatim-osm",
|
|
172
|
+
"name": "Nominatim / OpenStreetMap",
|
|
173
|
+
"description": "Free geocoding and reverse geocoding powered by OpenStreetMap — turn addresses into coordinates and back.",
|
|
174
|
+
"category": "maps",
|
|
175
|
+
"icon": "Map",
|
|
176
|
+
"docsUrl": "https://nominatim.org/release-docs/develop/api/Overview",
|
|
177
|
+
"baseUrl": "https://nominatim.openstreetmap.org",
|
|
178
|
+
"authType": "none",
|
|
179
|
+
"keyHeader": null,
|
|
180
|
+
"sampleEndpoints": [
|
|
181
|
+
"GET /search?q=London&format=json",
|
|
182
|
+
"GET /reverse?lat=51.5&lon=-0.1&format=json",
|
|
183
|
+
"GET /search?q=restaurants+near+times+square&format=json",
|
|
184
|
+
"GET /lookup?osm_ids=R146656&format=json"
|
|
185
|
+
],
|
|
186
|
+
"starterPrompts": [
|
|
187
|
+
"Build a location search tool with map preview",
|
|
188
|
+
"Create a distance calculator between any two places",
|
|
189
|
+
"Build a geocoding batch tool for CSV address lists",
|
|
190
|
+
"Make a neighborhood explorer that shows nearby points of interest"
|
|
191
|
+
],
|
|
192
|
+
"difficulty": "beginner"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "jsonplaceholder",
|
|
196
|
+
"name": "JSONPlaceholder",
|
|
197
|
+
"description": "A free fake REST API for testing and prototyping — perfect for learning HTTP requests with realistic data.",
|
|
198
|
+
"category": "data",
|
|
199
|
+
"icon": "Database",
|
|
200
|
+
"docsUrl": "https://jsonplaceholder.typicode.com/guide",
|
|
201
|
+
"baseUrl": "https://jsonplaceholder.typicode.com",
|
|
202
|
+
"authType": "none",
|
|
203
|
+
"keyHeader": null,
|
|
204
|
+
"sampleEndpoints": [
|
|
205
|
+
"GET /posts",
|
|
206
|
+
"GET /users/1",
|
|
207
|
+
"GET /posts/1/comments",
|
|
208
|
+
"POST /posts"
|
|
209
|
+
],
|
|
210
|
+
"starterPrompts": [
|
|
211
|
+
"Build a social media feed with posts and comments",
|
|
212
|
+
"Create a user directory with profile cards",
|
|
213
|
+
"Build a todo app backed by the API",
|
|
214
|
+
"Make a blog engine with CRUD operations"
|
|
215
|
+
],
|
|
216
|
+
"difficulty": "beginner"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"id": "the-dog-api",
|
|
220
|
+
"name": "The Dog API",
|
|
221
|
+
"description": "Browse every dog breed with images, temperaments, and stats — a fun API for dog lovers and quick prototypes.",
|
|
222
|
+
"category": "fun",
|
|
223
|
+
"icon": "Gamepad2",
|
|
224
|
+
"docsUrl": "https://docs.thedogapi.com",
|
|
225
|
+
"baseUrl": "https://api.thedogapi.com/v1",
|
|
226
|
+
"authType": "none",
|
|
227
|
+
"keyHeader": null,
|
|
228
|
+
"sampleEndpoints": [
|
|
229
|
+
"GET /breeds",
|
|
230
|
+
"GET /images/search?limit=10",
|
|
231
|
+
"GET /breeds/search?q=labrador",
|
|
232
|
+
"GET /breeds?limit=10&page=0"
|
|
233
|
+
],
|
|
234
|
+
"starterPrompts": [
|
|
235
|
+
"Build a dog breed encyclopedia with photos",
|
|
236
|
+
"Create a random dog image gallery",
|
|
237
|
+
"Build a dog breed quiz — guess the breed from the photo",
|
|
238
|
+
"Make a dog breed comparison tool with stats and images"
|
|
239
|
+
],
|
|
240
|
+
"difficulty": "beginner"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
@@ -94,4 +94,102 @@ describe('TaskClassifier', () => {
|
|
|
94
94
|
classifier.clearAgent('agent-1');
|
|
95
95
|
assert.equal(classifier.classify('agent-1'), 'medium');
|
|
96
96
|
});
|
|
97
|
+
|
|
98
|
+
describe('getUpdates()', () => {
|
|
99
|
+
it('should return empty when no agents have 40+ events', () => {
|
|
100
|
+
for (let i = 0; i < 39; i++) {
|
|
101
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
102
|
+
}
|
|
103
|
+
const updates = classifier.getUpdates();
|
|
104
|
+
assert.equal(updates.length, 0);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return classification when agent has enough events', () => {
|
|
108
|
+
for (let i = 0; i < 45; i++) {
|
|
109
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
110
|
+
}
|
|
111
|
+
const updates = classifier.getUpdates();
|
|
112
|
+
assert.equal(updates.length, 1);
|
|
113
|
+
assert.equal(updates[0].agentId, 'agent-1');
|
|
114
|
+
assert.equal(updates[0].tier, 'light');
|
|
115
|
+
assert.equal(updates[0].eventCount, 45);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should only report changes — no repeat when tier and count stable', () => {
|
|
119
|
+
for (let i = 0; i < 45; i++) {
|
|
120
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
121
|
+
}
|
|
122
|
+
// First call reports
|
|
123
|
+
const first = classifier.getUpdates();
|
|
124
|
+
assert.equal(first.length, 1);
|
|
125
|
+
|
|
126
|
+
// Second call with no new events — no report
|
|
127
|
+
const second = classifier.getUpdates();
|
|
128
|
+
assert.equal(second.length, 0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should report again when tier changes', () => {
|
|
132
|
+
// Start with read-only (light)
|
|
133
|
+
for (let i = 0; i < 45; i++) {
|
|
134
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
135
|
+
}
|
|
136
|
+
classifier.getUpdates(); // consume initial report
|
|
137
|
+
|
|
138
|
+
// Add heavy signals to shift classification
|
|
139
|
+
for (let i = 0; i < 50; i++) {
|
|
140
|
+
classifier.addEvent('agent-1', { type: 'error', text: 'TypeError: undefined' });
|
|
141
|
+
}
|
|
142
|
+
const updates = classifier.getUpdates();
|
|
143
|
+
assert.equal(updates.length, 1);
|
|
144
|
+
assert.equal(updates[0].tier, 'heavy');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should report again when event count delta reaches 20', () => {
|
|
148
|
+
for (let i = 0; i < 45; i++) {
|
|
149
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
150
|
+
}
|
|
151
|
+
classifier.getUpdates(); // consume initial report (eventCount=45)
|
|
152
|
+
|
|
153
|
+
// Add 20 more events (same tier, but eventCount delta = 20)
|
|
154
|
+
for (let i = 0; i < 20; i++) {
|
|
155
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `extra${i}.js` });
|
|
156
|
+
}
|
|
157
|
+
const updates = classifier.getUpdates();
|
|
158
|
+
assert.equal(updates.length, 1);
|
|
159
|
+
assert.equal(updates[0].eventCount, 65);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should not report when event count delta is less than 20', () => {
|
|
163
|
+
for (let i = 0; i < 45; i++) {
|
|
164
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
165
|
+
}
|
|
166
|
+
classifier.getUpdates(); // consume initial report (eventCount=45)
|
|
167
|
+
|
|
168
|
+
// Add only 10 more events — delta < 20, same tier
|
|
169
|
+
for (let i = 0; i < 10; i++) {
|
|
170
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `extra${i}.js` });
|
|
171
|
+
}
|
|
172
|
+
const updates = classifier.getUpdates();
|
|
173
|
+
assert.equal(updates.length, 0);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('clearAgent() broadcast state', () => {
|
|
178
|
+
it('should clear broadcast state so next getUpdates reports fresh', () => {
|
|
179
|
+
for (let i = 0; i < 45; i++) {
|
|
180
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
181
|
+
}
|
|
182
|
+
classifier.getUpdates(); // consume initial report
|
|
183
|
+
|
|
184
|
+
// Clear and re-populate
|
|
185
|
+
classifier.clearAgent('agent-1');
|
|
186
|
+
assert.equal(classifier._lastBroadcast['agent-1'], undefined);
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < 45; i++) {
|
|
189
|
+
classifier.addEvent('agent-1', { type: 'tool', tool: 'Read', input: `file${i}.js` });
|
|
190
|
+
}
|
|
191
|
+
const updates = classifier.getUpdates();
|
|
192
|
+
assert.equal(updates.length, 1); // Reports again because broadcast state was cleared
|
|
193
|
+
});
|
|
194
|
+
});
|
|
97
195
|
});
|
|
@@ -6,7 +6,8 @@ import assert from 'node:assert/strict';
|
|
|
6
6
|
import { Introducer } from '../src/introducer.js';
|
|
7
7
|
import { Registry } from '../src/registry.js';
|
|
8
8
|
import { StateManager } from '../src/state.js';
|
|
9
|
-
import {
|
|
9
|
+
import { MemoryStore } from '../src/memory.js';
|
|
10
|
+
import { mkdtempSync, readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
10
11
|
import { join } from 'path';
|
|
11
12
|
import { tmpdir } from 'os';
|
|
12
13
|
|
|
@@ -92,6 +93,76 @@ describe('Introducer', () => {
|
|
|
92
93
|
});
|
|
93
94
|
});
|
|
94
95
|
|
|
96
|
+
describe('Layer 7 memory injection', () => {
|
|
97
|
+
it('should append memory section at end of context when memory exists', () => {
|
|
98
|
+
const grooveDir = join(tmpDir, '.groove');
|
|
99
|
+
mkdirSync(grooveDir, { recursive: true });
|
|
100
|
+
const memory = new MemoryStore(grooveDir);
|
|
101
|
+
memory.addConstraint({ text: 'Never modify packages/daemon/index.js directly', category: 'hard' });
|
|
102
|
+
memory.addDiscovery({ role: 'backend', trigger: 'Cannot find module X', fix: 'npm install X', outcome: 'success' });
|
|
103
|
+
|
|
104
|
+
const daemon = { registry, projectDir: tmpDir, grooveDir, memory };
|
|
105
|
+
const intro = new Introducer(daemon);
|
|
106
|
+
|
|
107
|
+
const agent = registry.add({ role: 'backend', scope: ['src/api/**'] });
|
|
108
|
+
const ctx = intro.generateContext(agent, { hasTask: true });
|
|
109
|
+
|
|
110
|
+
assert.ok(ctx.includes('## Project Memory (auto-generated)'));
|
|
111
|
+
assert.ok(ctx.includes('Constraints (read carefully)'));
|
|
112
|
+
assert.ok(ctx.includes('Never modify packages/daemon/index.js'));
|
|
113
|
+
assert.ok(ctx.includes('Known Fixes for backend Role'));
|
|
114
|
+
assert.ok(ctx.includes('Cannot find module X'));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should not inject memory section when memory is empty', () => {
|
|
118
|
+
const grooveDir = join(tmpDir, '.groove');
|
|
119
|
+
mkdirSync(grooveDir, { recursive: true });
|
|
120
|
+
const memory = new MemoryStore(grooveDir);
|
|
121
|
+
|
|
122
|
+
const daemon = { registry, projectDir: tmpDir, grooveDir, memory };
|
|
123
|
+
const intro = new Introducer(daemon);
|
|
124
|
+
|
|
125
|
+
const agent = registry.add({ role: 'backend', scope: [] });
|
|
126
|
+
const ctx = intro.generateContext(agent);
|
|
127
|
+
|
|
128
|
+
assert.ok(!ctx.includes('## Project Memory'));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should not break spawn when memory is unavailable', () => {
|
|
132
|
+
const daemon = { registry, projectDir: tmpDir, grooveDir: tmpDir, memory: null };
|
|
133
|
+
const intro = new Introducer(daemon);
|
|
134
|
+
|
|
135
|
+
const agent = registry.add({ role: 'backend', scope: [] });
|
|
136
|
+
const ctx = intro.generateContext(agent);
|
|
137
|
+
|
|
138
|
+
// Should produce valid context without memory section
|
|
139
|
+
assert.ok(ctx.includes('backend'));
|
|
140
|
+
assert.ok(!ctx.includes('## Project Memory'));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should enforce 4K character budget on memory section', () => {
|
|
144
|
+
const grooveDir = join(tmpDir, '.groove');
|
|
145
|
+
mkdirSync(grooveDir, { recursive: true });
|
|
146
|
+
const memory = new MemoryStore(grooveDir);
|
|
147
|
+
// Add many constraints to exceed budget
|
|
148
|
+
for (let i = 0; i < 40; i++) {
|
|
149
|
+
memory.addConstraint({ text: `Rule ${i}: never touch file-${i}.config.js in the project root directory`, category: 'hard' });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const daemon = { registry, projectDir: tmpDir, grooveDir, memory };
|
|
153
|
+
const intro = new Introducer(daemon);
|
|
154
|
+
|
|
155
|
+
const agent = registry.add({ role: 'backend', scope: [] });
|
|
156
|
+
const ctx = intro.generateContext(agent);
|
|
157
|
+
|
|
158
|
+
// Memory section should exist but be truncated
|
|
159
|
+
const memStart = ctx.indexOf('## Project Memory');
|
|
160
|
+
assert.ok(memStart !== -1);
|
|
161
|
+
const memSection = ctx.slice(memStart);
|
|
162
|
+
assert.ok(memSection.length <= 4003); // 4000 + "..." adjustment
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
95
166
|
describe('CLAUDE.md injection', () => {
|
|
96
167
|
it('should inject GROOVE section into existing CLAUDE.md', () => {
|
|
97
168
|
writeFileSync(join(tmpDir, 'CLAUDE.md'), '# My Project\n\nSome content.\n');
|
|
@@ -6,6 +6,7 @@ import assert from 'node:assert/strict';
|
|
|
6
6
|
import { Journalist } from '../src/journalist.js';
|
|
7
7
|
import { Registry } from '../src/registry.js';
|
|
8
8
|
import { StateManager } from '../src/state.js';
|
|
9
|
+
import { MemoryStore } from '../src/memory.js';
|
|
9
10
|
import { mkdtempSync, writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs';
|
|
10
11
|
import { join } from 'path';
|
|
11
12
|
import { tmpdir } from 'os';
|
|
@@ -252,4 +253,120 @@ describe('Journalist', () => {
|
|
|
252
253
|
assert.equal(status.running, false);
|
|
253
254
|
});
|
|
254
255
|
});
|
|
256
|
+
|
|
257
|
+
describe('_extractDiscoveries', () => {
|
|
258
|
+
it('should extract error→fix pairs from filtered logs', () => {
|
|
259
|
+
const { daemon, grooveDir } = createMockDaemon();
|
|
260
|
+
daemon.memory = new MemoryStore(grooveDir);
|
|
261
|
+
const journalist = new Journalist(daemon);
|
|
262
|
+
|
|
263
|
+
const filteredLogs = {
|
|
264
|
+
a1: {
|
|
265
|
+
agent: { id: 'a1', role: 'backend' },
|
|
266
|
+
entries: [
|
|
267
|
+
{ type: 'error', text: 'Error: Cannot find module gray-matter' },
|
|
268
|
+
{ type: 'tool', tool: 'Edit', input: 'package.json — added gray-matter dependency' },
|
|
269
|
+
],
|
|
270
|
+
explorationEntries: [],
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
journalist._extractDiscoveries(filteredLogs);
|
|
275
|
+
|
|
276
|
+
const discoveries = daemon.memory.listDiscoveries();
|
|
277
|
+
assert.equal(discoveries.length, 1);
|
|
278
|
+
assert.ok(discoveries[0].trigger.includes('Cannot find module'));
|
|
279
|
+
assert.ok(discoveries[0].fix.includes('package.json'));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should not crash when memory is unavailable', () => {
|
|
283
|
+
const { daemon } = createMockDaemon();
|
|
284
|
+
daemon.memory = null;
|
|
285
|
+
const journalist = new Journalist(daemon);
|
|
286
|
+
|
|
287
|
+
// Should not throw
|
|
288
|
+
journalist._extractDiscoveries({
|
|
289
|
+
a1: { agent: { id: 'a1', role: 'backend' }, entries: [], explorationEntries: [] },
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should skip entries without error signals', () => {
|
|
294
|
+
const { daemon, grooveDir } = createMockDaemon();
|
|
295
|
+
daemon.memory = new MemoryStore(grooveDir);
|
|
296
|
+
const journalist = new Journalist(daemon);
|
|
297
|
+
|
|
298
|
+
const filteredLogs = {
|
|
299
|
+
a1: {
|
|
300
|
+
agent: { id: 'a1', role: 'backend' },
|
|
301
|
+
entries: [
|
|
302
|
+
{ type: 'tool', tool: 'Write', input: 'src/index.js' },
|
|
303
|
+
{ type: 'tool', tool: 'Edit', input: 'src/api.js' },
|
|
304
|
+
],
|
|
305
|
+
explorationEntries: [],
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
journalist._extractDiscoveries(filteredLogs);
|
|
310
|
+
assert.equal(daemon.memory.listDiscoveries().length, 0);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('_extractConstraints', () => {
|
|
315
|
+
it('should no-op since auto-extraction is disabled', () => {
|
|
316
|
+
const { daemon, grooveDir } = createMockDaemon();
|
|
317
|
+
daemon.memory = new MemoryStore(grooveDir);
|
|
318
|
+
const journalist = new Journalist(daemon);
|
|
319
|
+
|
|
320
|
+
const filteredLogs = {
|
|
321
|
+
a1: {
|
|
322
|
+
agent: { id: 'a1', role: 'backend' },
|
|
323
|
+
entries: [
|
|
324
|
+
{
|
|
325
|
+
type: 'thinking',
|
|
326
|
+
text: 'I should never modify packages/daemon/index.js directly because it is auto-generated from the template',
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
explorationEntries: [],
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
journalist._extractConstraints(filteredLogs);
|
|
334
|
+
|
|
335
|
+
const constraints = daemon.memory.listConstraints();
|
|
336
|
+
assert.equal(constraints.length, 0);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should skip non-project-specific constraints', () => {
|
|
340
|
+
const { daemon, grooveDir } = createMockDaemon();
|
|
341
|
+
daemon.memory = new MemoryStore(grooveDir);
|
|
342
|
+
const journalist = new Journalist(daemon);
|
|
343
|
+
|
|
344
|
+
const filteredLogs = {
|
|
345
|
+
a1: {
|
|
346
|
+
agent: { id: 'a1', role: 'backend' },
|
|
347
|
+
entries: [
|
|
348
|
+
{
|
|
349
|
+
type: 'thinking',
|
|
350
|
+
text: 'I must always write clean code and follow best practices for readability',
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
explorationEntries: [],
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
journalist._extractConstraints(filteredLogs);
|
|
358
|
+
// Generic advice should be skipped (no file/config references)
|
|
359
|
+
assert.equal(daemon.memory.listConstraints().length, 0);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should not crash when memory is unavailable', () => {
|
|
363
|
+
const { daemon } = createMockDaemon();
|
|
364
|
+
daemon.memory = null;
|
|
365
|
+
const journalist = new Journalist(daemon);
|
|
366
|
+
|
|
367
|
+
journalist._extractConstraints({
|
|
368
|
+
a1: { agent: { id: 'a1', role: 'backend' }, entries: [], explorationEntries: [] },
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
255
372
|
});
|
|
@@ -142,9 +142,45 @@ describe('MemoryStore', () => {
|
|
|
142
142
|
it('sanitizes role names for filesystem safety', () => {
|
|
143
143
|
const ok = memory.appendHandoffBrief('../evil/role', { brief: 'test' });
|
|
144
144
|
assert.equal(ok, true);
|
|
145
|
-
// File should be created with safe name, no path traversal
|
|
146
145
|
assert.ok(existsSync(tmpDir));
|
|
147
146
|
});
|
|
147
|
+
|
|
148
|
+
it('scopes chains by workspace', () => {
|
|
149
|
+
const wsA = join(tmpDir, '..', 'electron-app');
|
|
150
|
+
const wsB = join(tmpDir, '..', 'agents-team');
|
|
151
|
+
memory.appendHandoffBrief('backend', { brief: 'electron backend work' }, wsA);
|
|
152
|
+
memory.appendHandoffBrief('backend', { brief: 'agents backend work' }, wsB);
|
|
153
|
+
|
|
154
|
+
const chainA = memory.getHandoffChain('backend', wsA);
|
|
155
|
+
const chainB = memory.getHandoffChain('backend', wsB);
|
|
156
|
+
assert.equal(chainA.length, 1);
|
|
157
|
+
assert.equal(chainB.length, 1);
|
|
158
|
+
assert.match(chainA[0].body, /electron backend/);
|
|
159
|
+
assert.match(chainB[0].body, /agents backend/);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('workspace chains are independent from root chains', () => {
|
|
163
|
+
const ws = join(tmpDir, '..', 'my-workspace');
|
|
164
|
+
memory.appendHandoffBrief('frontend', { brief: 'root work' });
|
|
165
|
+
memory.appendHandoffBrief('frontend', { brief: 'workspace work' }, ws);
|
|
166
|
+
|
|
167
|
+
assert.equal(memory.getHandoffChain('frontend').length, 1);
|
|
168
|
+
assert.equal(memory.getHandoffChain('frontend', ws).length, 1);
|
|
169
|
+
assert.match(memory.getRecentHandoffMarkdown('frontend'), /root work/);
|
|
170
|
+
assert.match(memory.getRecentHandoffMarkdown('frontend', 3, 4000, ws), /workspace work/);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('listHandoffRoles scoped to workspace', () => {
|
|
174
|
+
const ws = join(tmpDir, '..', 'electron-app');
|
|
175
|
+
memory.appendHandoffBrief('backend', { brief: 'root' });
|
|
176
|
+
memory.appendHandoffBrief('frontend', { brief: 'ws' }, ws);
|
|
177
|
+
|
|
178
|
+
const rootRoles = memory.listHandoffRoles();
|
|
179
|
+
const wsRoles = memory.listHandoffRoles(ws);
|
|
180
|
+
assert.ok(rootRoles.includes('backend'));
|
|
181
|
+
assert.ok(!rootRoles.includes('frontend'));
|
|
182
|
+
assert.ok(wsRoles.includes('frontend'));
|
|
183
|
+
});
|
|
148
184
|
});
|
|
149
185
|
|
|
150
186
|
describe('discoveries', () => {
|