@aexol/spectral 0.9.230 → 0.9.231

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 (172) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/designer/data/skills/blog-post.d.ts +3 -0
  3. package/dist/designer/data/skills/blog-post.d.ts.map +1 -0
  4. package/dist/designer/data/skills/blog-post.js +84 -0
  5. package/dist/designer/data/skills/blog-post.ts +87 -0
  6. package/dist/designer/data/skills/critique.d.ts +3 -0
  7. package/dist/designer/data/skills/critique.d.ts.map +1 -0
  8. package/dist/designer/data/skills/critique.js +109 -0
  9. package/dist/designer/data/skills/critique.ts +112 -0
  10. package/dist/designer/data/skills/dashboard.d.ts +3 -0
  11. package/dist/designer/data/skills/dashboard.d.ts.map +1 -0
  12. package/dist/designer/data/skills/dashboard.js +165 -0
  13. package/dist/designer/data/skills/dashboard.ts +168 -0
  14. package/dist/designer/data/skills/design-brief.d.ts +3 -0
  15. package/dist/designer/data/skills/design-brief.d.ts.map +1 -0
  16. package/dist/designer/data/skills/design-brief.js +141 -0
  17. package/dist/designer/data/skills/design-brief.ts +144 -0
  18. package/dist/designer/data/skills/design-skill.d.ts +10 -0
  19. package/dist/designer/data/skills/design-skill.d.ts.map +1 -0
  20. package/dist/designer/data/skills/design-skill.js +1 -0
  21. package/dist/designer/data/skills/design-skill.ts +9 -0
  22. package/dist/designer/data/skills/docs-page.d.ts +3 -0
  23. package/dist/designer/data/skills/docs-page.d.ts.map +1 -0
  24. package/dist/designer/data/skills/docs-page.js +68 -0
  25. package/dist/designer/data/skills/docs-page.ts +71 -0
  26. package/dist/designer/data/skills/index.d.ts +3 -0
  27. package/dist/designer/data/skills/index.d.ts.map +1 -0
  28. package/dist/designer/data/skills/index.js +28 -0
  29. package/dist/designer/data/skills/index.ts +30 -0
  30. package/dist/designer/data/skills/magazine-poster.d.ts +3 -0
  31. package/dist/designer/data/skills/magazine-poster.d.ts.map +1 -0
  32. package/dist/designer/data/skills/magazine-poster.js +70 -0
  33. package/dist/designer/data/skills/magazine-poster.ts +73 -0
  34. package/dist/designer/data/skills/mobile-app.d.ts +3 -0
  35. package/dist/designer/data/skills/mobile-app.d.ts.map +1 -0
  36. package/dist/designer/data/skills/mobile-app.js +229 -0
  37. package/dist/designer/data/skills/mobile-app.ts +232 -0
  38. package/dist/designer/data/skills/motion-frames.d.ts +3 -0
  39. package/dist/designer/data/skills/motion-frames.d.ts.map +1 -0
  40. package/dist/designer/data/skills/motion-frames.js +121 -0
  41. package/dist/designer/data/skills/motion-frames.ts +124 -0
  42. package/dist/designer/data/skills/pricing-page.d.ts +3 -0
  43. package/dist/designer/data/skills/pricing-page.d.ts.map +1 -0
  44. package/dist/designer/data/skills/pricing-page.js +76 -0
  45. package/dist/designer/data/skills/pricing-page.ts +79 -0
  46. package/dist/designer/data/skills/saas-landing.d.ts +3 -0
  47. package/dist/designer/data/skills/saas-landing.d.ts.map +1 -0
  48. package/dist/designer/data/skills/saas-landing.js +71 -0
  49. package/dist/designer/data/skills/saas-landing.ts +74 -0
  50. package/dist/designer/data/skills/slide-deck.d.ts +3 -0
  51. package/dist/designer/data/skills/slide-deck.d.ts.map +1 -0
  52. package/dist/designer/data/skills/slide-deck.js +213 -0
  53. package/dist/designer/data/skills/slide-deck.ts +216 -0
  54. package/dist/designer/data/skills/social-carousel.d.ts +3 -0
  55. package/dist/designer/data/skills/social-carousel.d.ts.map +1 -0
  56. package/dist/designer/data/skills/social-carousel.js +135 -0
  57. package/dist/designer/data/skills/social-carousel.ts +138 -0
  58. package/dist/designer/data/skills/web-prototype.d.ts +3 -0
  59. package/dist/designer/data/skills/web-prototype.d.ts.map +1 -0
  60. package/dist/designer/data/skills/web-prototype.js +159 -0
  61. package/dist/designer/data/skills/web-prototype.ts +162 -0
  62. package/dist/designer/skills.d.ts +4 -10
  63. package/dist/designer/skills.d.ts.map +1 -1
  64. package/dist/designer/skills.js +2 -1643
  65. package/dist/extensions/wizard/index.js +9 -1
  66. package/dist/generated/parser/ast/nodes.d.ts +115 -0
  67. package/dist/generated/parser/ast/nodes.d.ts.map +1 -0
  68. package/dist/generated/parser/ast/nodes.js +8 -0
  69. package/dist/generated/parser/lexer/lexer.d.ts +28 -0
  70. package/dist/generated/parser/lexer/lexer.d.ts.map +1 -0
  71. package/dist/generated/parser/{lexer.js → lexer/lexer.js} +1 -180
  72. package/dist/generated/parser/{lexer.d.ts → lexer/tokens.d.ts} +5 -24
  73. package/dist/generated/parser/lexer/tokens.d.ts.map +1 -0
  74. package/dist/generated/parser/lexer/tokens.js +187 -0
  75. package/dist/generated/parser/mod.d.ts +5 -7
  76. package/dist/generated/parser/mod.d.ts.map +1 -1
  77. package/dist/generated/parser/mod.js +2 -1
  78. package/dist/generated/parser/parser/cursor.d.ts +47 -0
  79. package/dist/generated/parser/parser/cursor.d.ts.map +1 -0
  80. package/dist/generated/parser/parser/cursor.js +12 -0
  81. package/dist/generated/parser/parser/definitions.d.ts +16 -0
  82. package/dist/generated/parser/parser/definitions.d.ts.map +1 -0
  83. package/dist/generated/parser/parser/definitions.js +353 -0
  84. package/dist/generated/parser/parser/expressions.d.ts +17 -0
  85. package/dist/generated/parser/parser/expressions.d.ts.map +1 -0
  86. package/dist/generated/parser/parser/expressions.js +165 -0
  87. package/dist/generated/parser/parser.d.ts +18 -133
  88. package/dist/generated/parser/parser.d.ts.map +1 -1
  89. package/dist/generated/parser/parser.js +9 -505
  90. package/dist/generated/parser/tokenizer.d.ts +1 -2
  91. package/dist/generated/parser/tokenizer.d.ts.map +1 -1
  92. package/dist/generated/parser/tokenizer.js +2 -1
  93. package/dist/generated/parser/validator.d.ts +1 -1
  94. package/dist/generated/parser/validator.d.ts.map +1 -1
  95. package/dist/mcp/glimpse-ui.js +9 -1
  96. package/dist/relay/dispatcher.d.ts +11 -104
  97. package/dist/relay/dispatcher.d.ts.map +1 -1
  98. package/dist/relay/dispatcher.js +10 -1130
  99. package/dist/relay/rest.d.ts +31 -0
  100. package/dist/relay/rest.d.ts.map +1 -0
  101. package/dist/relay/rest.js +85 -0
  102. package/dist/relay/routing.d.ts +109 -0
  103. package/dist/relay/routing.d.ts.map +1 -0
  104. package/dist/relay/routing.js +669 -0
  105. package/dist/relay/wizard-login.d.ts +17 -0
  106. package/dist/relay/wizard-login.d.ts.map +1 -0
  107. package/dist/relay/wizard-login.js +419 -0
  108. package/dist/sdk/agent-core/agent-loop.d.ts +2 -9
  109. package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
  110. package/dist/sdk/agent-core/agent-loop.js +4 -288
  111. package/dist/sdk/agent-core/tool-execution.d.ts +33 -0
  112. package/dist/sdk/agent-core/tool-execution.d.ts.map +1 -0
  113. package/dist/sdk/agent-core/tool-execution.js +300 -0
  114. package/dist/sdk/ai/env-api-keys.js +9 -1
  115. package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts +48 -0
  116. package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts.map +1 -0
  117. package/dist/sdk/ai/providers/openai/reasoning-replay.js +209 -0
  118. package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
  119. package/dist/sdk/ai/providers/openai-completions.js +1 -203
  120. package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +6 -118
  121. package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
  122. package/dist/sdk/coding-agent/core/compaction/compaction.js +11 -733
  123. package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts +58 -0
  124. package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts.map +1 -0
  125. package/dist/sdk/coding-agent/core/compaction/cut-points.js +191 -0
  126. package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts +28 -0
  127. package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts.map +1 -0
  128. package/dist/sdk/coding-agent/core/compaction/dedupe.js +156 -0
  129. package/dist/sdk/coding-agent/core/compaction/summarize.d.ts +21 -0
  130. package/dist/sdk/coding-agent/core/compaction/summarize.d.ts.map +1 -0
  131. package/dist/sdk/coding-agent/core/compaction/summarize.js +181 -0
  132. package/dist/sdk/coding-agent/core/compaction/tokens.d.ts +47 -0
  133. package/dist/sdk/coding-agent/core/compaction/tokens.d.ts.map +1 -0
  134. package/dist/sdk/coding-agent/core/compaction/tokens.js +224 -0
  135. package/dist/sdk/coding-agent/core/extensions/session-events.d.ts +157 -0
  136. package/dist/sdk/coding-agent/core/extensions/session-events.d.ts.map +1 -0
  137. package/dist/sdk/coding-agent/core/extensions/session-events.js +13 -0
  138. package/dist/sdk/coding-agent/core/extensions/types.d.ts +6 -365
  139. package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
  140. package/dist/sdk/coding-agent/core/extensions/types.js +2 -10
  141. package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts +219 -0
  142. package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts.map +1 -0
  143. package/dist/sdk/coding-agent/core/extensions/ui-types.js +4 -0
  144. package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts +115 -0
  145. package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts.map +1 -0
  146. package/dist/sdk/coding-agent/core/package/default-package-manager.js +1184 -0
  147. package/dist/sdk/coding-agent/core/package/discovery.d.ts +20 -0
  148. package/dist/sdk/coding-agent/core/package/discovery.d.ts.map +1 -0
  149. package/dist/sdk/coding-agent/core/package/discovery.js +283 -0
  150. package/dist/sdk/coding-agent/core/package/ignore.d.ts +29 -0
  151. package/dist/sdk/coding-agent/core/package/ignore.d.ts.map +1 -0
  152. package/dist/sdk/coding-agent/core/package/ignore.js +235 -0
  153. package/dist/sdk/coding-agent/core/package/types.d.ts +128 -0
  154. package/dist/sdk/coding-agent/core/package/types.d.ts.map +1 -0
  155. package/dist/sdk/coding-agent/core/package/types.js +18 -0
  156. package/dist/sdk/coding-agent/core/package/update-check.d.ts +43 -0
  157. package/dist/sdk/coding-agent/core/package/update-check.d.ts.map +1 -0
  158. package/dist/sdk/coding-agent/core/package/update-check.js +253 -0
  159. package/dist/sdk/coding-agent/core/package-manager.d.ts +2 -188
  160. package/dist/sdk/coding-agent/core/package-manager.d.ts.map +1 -1
  161. package/dist/sdk/coding-agent/core/package-manager.js +1 -1940
  162. package/dist/sdk/coding-agent/core/session/migrate.d.ts +18 -0
  163. package/dist/sdk/coding-agent/core/session/migrate.d.ts.map +1 -0
  164. package/dist/sdk/coding-agent/core/session/migrate.js +78 -0
  165. package/dist/sdk/coding-agent/core/session/types.d.ts +165 -0
  166. package/dist/sdk/coding-agent/core/session/types.d.ts.map +1 -0
  167. package/dist/sdk/coding-agent/core/session/types.js +4 -0
  168. package/dist/sdk/coding-agent/core/session-manager.d.ts +3 -161
  169. package/dist/sdk/coding-agent/core/session-manager.d.ts.map +1 -1
  170. package/dist/sdk/coding-agent/core/session-manager.js +4 -72
  171. package/package.json +2 -2
  172. package/dist/generated/parser/lexer.d.ts.map +0 -1
@@ -0,0 +1,669 @@
1
+ /**
2
+ * Relay route table (CLI side).
3
+ *
4
+ * `matchRoute` is the pure path matcher translating `method + path` into a
5
+ * `RouteMatch`; `dispatchRoute` is the inner switch that calls the pure REST
6
+ * handlers (`handlers/projects.ts`, `handlers/sessions.ts`, ...). Extracted
7
+ * from `relay/dispatcher.ts` — `handleRestRequest` (`relay/rest.ts`) owns the
8
+ * single try/catch boundary around `dispatchRoute`, and the dispatcher
9
+ * re-exports `matchRoute` + the request-deps types so existing imports keep
10
+ * working.
11
+ */
12
+ import { homedir } from "node:os";
13
+ import { BadRequestError } from "../server/handlers/errors.js";
14
+ import { handleListMcpStatus } from "../server/handlers/mcp-status.js";
15
+ import { handleReloadMcpConfig } from "../server/handlers/mcp-reload.js";
16
+ import { handlePathAutocomplete } from "../server/handlers/paths-autocomplete.js";
17
+ import { handleGetHomeDirectory } from "../server/handlers/paths-home.js";
18
+ import { handleListDirectory } from "../server/handlers/paths-list.js";
19
+ import { handleSearchProjectFiles } from "../server/handlers/files-search.js";
20
+ import { handleBindStudioProject, handleUnbindStudioProject, handleCleanupProjectSessions, handleCreateProject, handleDeleteProject, handleGetProjectObservations, handleListProjectsAsync, handleListSessionsByProjectAsync, handleUpdateProject, } from "../server/handlers/projects.js";
21
+ import { handleCompactSession, handleCleanupAllSessions, handleCreateSession, handleDeleteSession, handleForkSession, handleRememberAndDeleteSession, handleGetSessionDetailAsync, handleGetSessionMessagesAsync, handleGetSessionMemoryDetails, handleGetSessionMemoryStatus, handleUpdateSession, handleGetSessionActiveAgentAsync, handleUpdateSessionActiveAgentAsync, } from "../server/handlers/sessions.js";
22
+ import { handleClearPromptQueue, handleEnqueuePrompt, handleGetPromptQueue, handleRemovePrompt, } from "../server/handlers/queue.js";
23
+ import { handleGetAgentSettingsAsync, handleListAgents, handlePutAgentSettingsAsync, } from "../server/handlers/agent-settings.js";
24
+ import { handleGetSettingsAsync, handlePutSettingsAsync, } from "../server/handlers/settings.js";
25
+ import { handleGetPromptMutationSettings, handlePutPromptMutationSettings, } from "../server/handlers/prompt-mutation-settings.js";
26
+ import { handleKillDevProcess, handleKillDetectedDevServer, handleListDevProcesses, handleRestartDevProcess, handleScanDevServers, handleStartDevProcess, } from "../server/handlers/dev-processes.js";
27
+ import { startWizardLogin } from "./wizard-login.js";
28
+ /**
29
+ * Inline path matcher. Returns `null` for any path/method combination we
30
+ * don't recognise; the caller turns that into a `404 Unknown route`.
31
+ *
32
+ * Intentionally literal — a regex table would be marginally fancier but
33
+ * also marginally slower and harder to read for ~11 routes.
34
+ */
35
+ export function matchRoute(method, path) {
36
+ // Strip query string for path matching but keep it for the handler.
37
+ const qIdx = path.indexOf("?");
38
+ const cleanPath = qIdx === -1 ? path : path.slice(0, qIdx);
39
+ const query = qIdx >= 0 ? new URLSearchParams(path.slice(qIdx + 1)) : undefined;
40
+ // /api/mcp-status
41
+ if (cleanPath === "/api/mcp-status") {
42
+ if (method === "GET")
43
+ return { route: "list_mcp_status" };
44
+ return null;
45
+ }
46
+ if (cleanPath === "/api/mcp-reload") {
47
+ if (method === "POST")
48
+ return { route: "reload_mcp_config" };
49
+ return null;
50
+ }
51
+ if (cleanPath === "/api/native-extensions/refresh") {
52
+ if (method === "POST")
53
+ return { route: "refresh_native_extensions" };
54
+ return null;
55
+ }
56
+ if (cleanPath === "/api/cloud-agents/refresh") {
57
+ if (method === "POST")
58
+ return { route: "refresh_cloud_agents" };
59
+ return null;
60
+ }
61
+ if (cleanPath === "/api/wizard/login") {
62
+ if (method === "POST")
63
+ return { route: "wizard_login" };
64
+ return null;
65
+ }
66
+ // /api/settings
67
+ if (cleanPath === "/api/settings") {
68
+ if (method === "GET")
69
+ return { route: "get_settings" };
70
+ if (method === "PUT")
71
+ return { route: "put_settings" };
72
+ return null;
73
+ }
74
+ // /api/agent-settings
75
+ if (cleanPath === "/api/agent-settings") {
76
+ if (method === "GET")
77
+ return { route: "get_agent_settings" };
78
+ if (method === "PUT")
79
+ return { route: "put_agent_settings" };
80
+ return null;
81
+ }
82
+ if (cleanPath === "/api/prompt-mutation-settings") {
83
+ if (method === "GET")
84
+ return { route: "get_prompt_mutation_settings" };
85
+ if (method === "PUT")
86
+ return { route: "put_prompt_mutation_settings" };
87
+ return null;
88
+ }
89
+ // /api/agents
90
+ if (cleanPath === "/api/agents") {
91
+ if (method === "GET")
92
+ return { route: "list_agents", query };
93
+ return null;
94
+ }
95
+ // /api/paths/autocomplete
96
+ if (cleanPath === "/api/paths/autocomplete") {
97
+ if (method === "GET")
98
+ return { route: "list_path_autocomplete", query };
99
+ return null;
100
+ }
101
+ // /api/paths/list
102
+ if (cleanPath === "/api/paths/list") {
103
+ if (method === "POST")
104
+ return { route: "list_directory" };
105
+ return null;
106
+ }
107
+ // /api/paths/home
108
+ if (cleanPath === "/api/paths/home") {
109
+ if (method === "POST")
110
+ return { route: "get_home_directory" };
111
+ return null;
112
+ }
113
+ // /api/projects
114
+ if (cleanPath === "/api/projects") {
115
+ if (method === "GET")
116
+ return { route: "list_projects" };
117
+ if (method === "POST")
118
+ return { route: "create_project" };
119
+ return null;
120
+ }
121
+ // /api/sessions
122
+ if (cleanPath === "/api/sessions") {
123
+ if (method === "POST")
124
+ return { route: "create_session" };
125
+ return null;
126
+ }
127
+ // /api/projects/:id/files/search
128
+ const projectFilesMatch = /^\/api\/projects\/([^/]+)\/files\/search$/.exec(cleanPath);
129
+ if (projectFilesMatch) {
130
+ const id = decodeURIComponent(projectFilesMatch[1]);
131
+ if (method === "GET")
132
+ return { route: "search_project_files", id, query };
133
+ return null;
134
+ }
135
+ // /api/projects/:id/bind-studio and /api/projects/:id/unbind-studio
136
+ const bindStudioMatch = /^\/api\/projects\/([^/]+)\/bind-studio$/.exec(cleanPath);
137
+ if (bindStudioMatch) {
138
+ const id = decodeURIComponent(bindStudioMatch[1]);
139
+ if (method === "POST")
140
+ return { route: "bind_studio", id };
141
+ return null;
142
+ }
143
+ const unbindStudioMatch = /^\/api\/projects\/([^/]+)\/unbind-studio$/.exec(cleanPath);
144
+ if (unbindStudioMatch) {
145
+ const id = decodeURIComponent(unbindStudioMatch[1]);
146
+ if (method === "POST")
147
+ return { route: "unbind_studio", id };
148
+ return null;
149
+ }
150
+ // /api/projects/:id/observations
151
+ const projectObsMatch = /^\/api\/projects\/([^/]+)\/observations$/.exec(cleanPath);
152
+ if (projectObsMatch) {
153
+ const id = decodeURIComponent(projectObsMatch[1]);
154
+ if (method === "GET")
155
+ return { route: "get_project_observations", id };
156
+ return null;
157
+ }
158
+ // /api/projects/:id and /api/projects/:id/sessions
159
+ const projectMatch = /^\/api\/projects\/([^/]+)(\/sessions)?$/.exec(cleanPath);
160
+ if (projectMatch) {
161
+ const id = decodeURIComponent(projectMatch[1]);
162
+ const isSessions = projectMatch[2] === "/sessions";
163
+ if (isSessions) {
164
+ if (method === "GET")
165
+ return { route: "list_project_sessions", id };
166
+ return null;
167
+ }
168
+ if (method === "PATCH")
169
+ return { route: "update_project", id };
170
+ if (method === "DELETE")
171
+ return { route: "delete_project", id };
172
+ return null;
173
+ }
174
+ // /api/projects/:id/cleanup-sessions
175
+ const projectCleanupMatch = /^\/api\/projects\/([^/]+)\/cleanup-sessions$/.exec(cleanPath);
176
+ if (projectCleanupMatch) {
177
+ const id = decodeURIComponent(projectCleanupMatch[1]);
178
+ if (method === "POST")
179
+ return { route: "cleanup_project_sessions", id };
180
+ return null;
181
+ }
182
+ // /api/sessions/cleanup (must precede the /api/sessions/:id matches —
183
+ // "cleanup" would otherwise be captured as an :id)
184
+ if (cleanPath === "/api/sessions/cleanup") {
185
+ if (method === "POST")
186
+ return { route: "cleanup_all_sessions" };
187
+ return null;
188
+ }
189
+ // /api/sessions/:id
190
+ const sessionMessagesMatch = /^\/api\/sessions\/([^/]+)\/messages$/.exec(cleanPath);
191
+ if (sessionMessagesMatch) {
192
+ const id = decodeURIComponent(sessionMessagesMatch[1]);
193
+ if (method === "GET")
194
+ return { route: "get_session_messages", id, query };
195
+ return null;
196
+ }
197
+ const sessionMatch = /^\/api\/sessions\/([^/]+)$/.exec(cleanPath);
198
+ if (sessionMatch) {
199
+ const id = decodeURIComponent(sessionMatch[1]);
200
+ if (method === "GET")
201
+ return { route: "get_session", id };
202
+ if (method === "PATCH")
203
+ return { route: "update_session", id };
204
+ if (method === "DELETE")
205
+ return { route: "delete_session", id };
206
+ return null;
207
+ }
208
+ const sessionAgentMatch = /^\/api\/sessions\/([^/]+)\/active-agent$/.exec(cleanPath);
209
+ if (sessionAgentMatch) {
210
+ const id = decodeURIComponent(sessionAgentMatch[1]);
211
+ if (method === "GET")
212
+ return { route: "get_session_active_agent", id };
213
+ if (method === "PUT")
214
+ return { route: "update_session_active_agent", id };
215
+ return null;
216
+ }
217
+ // /api/sessions/:id/memory/details and /api/sessions/:id/memory and /api/sessions/:id/compact
218
+ const sessionActionMatch = /^\/api\/sessions\/([^/]+)\/(memory(?:\/details)?|compact)$/.exec(cleanPath);
219
+ if (sessionActionMatch) {
220
+ const id = decodeURIComponent(sessionActionMatch[1]);
221
+ const action = sessionActionMatch[2];
222
+ if (action === "memory" && method === "GET")
223
+ return { route: "get_session_memory", id };
224
+ if (action === "memory/details" && method === "GET")
225
+ return { route: "get_session_memory_details", id };
226
+ if (action === "compact" && method === "POST")
227
+ return { route: "compact_session", id };
228
+ return null;
229
+ }
230
+ // /api/sessions/:id/remember-and-delete
231
+ const rememberDeleteMatch = /^\/api\/sessions\/([^/]+)\/remember-and-delete$/.exec(cleanPath);
232
+ if (rememberDeleteMatch) {
233
+ const id = decodeURIComponent(rememberDeleteMatch[1]);
234
+ if (method === "POST")
235
+ return { route: "remember_and_delete_session", id };
236
+ return null;
237
+ }
238
+ // /api/sessions/:id/fork
239
+ const forkMatch = /^\/api\/sessions\/([^/]+)\/fork$/.exec(cleanPath);
240
+ if (forkMatch) {
241
+ const id = decodeURIComponent(forkMatch[1]);
242
+ if (method === "POST")
243
+ return { route: "fork_session", id };
244
+ return null;
245
+ }
246
+ // /api/sessions/:id/queue and /api/sessions/:id/queue/:itemId
247
+ const queueMatch = /^\/api\/sessions\/([^/]+)\/queue(\/([^/]+))?$/.exec(cleanPath);
248
+ if (queueMatch) {
249
+ const id = decodeURIComponent(queueMatch[1]);
250
+ const itemId = queueMatch[3] ? decodeURIComponent(queueMatch[3]) : undefined;
251
+ if (!itemId) {
252
+ if (method === "GET")
253
+ return { route: "get_prompt_queue", id };
254
+ if (method === "POST")
255
+ return { route: "enqueue_prompt", id };
256
+ if (method === "DELETE")
257
+ return { route: "clear_prompt_queue", id };
258
+ }
259
+ else {
260
+ if (method === "DELETE")
261
+ return { route: "remove_prompt", id, itemId };
262
+ }
263
+ return null;
264
+ }
265
+ // /api/dev-processes and /api/dev-processes/:id
266
+ if (cleanPath === "/api/dev-processes") {
267
+ if (method === "GET")
268
+ return { route: "list_dev_processes" };
269
+ if (method === "POST")
270
+ return { route: "start_dev_process" };
271
+ return null;
272
+ }
273
+ const restartDevProcessMatch = /^\/api\/dev-processes\/([^/]+)\/restart$/.exec(cleanPath);
274
+ if (restartDevProcessMatch) {
275
+ const id = decodeURIComponent(restartDevProcessMatch[1]);
276
+ if (method === "POST")
277
+ return { route: "restart_dev_process", id };
278
+ return null;
279
+ }
280
+ const devProcessMatch = /^\/api\/dev-processes\/([^/]+)$/.exec(cleanPath);
281
+ if (devProcessMatch) {
282
+ const id = decodeURIComponent(devProcessMatch[1]);
283
+ if (method === "DELETE")
284
+ return { route: "kill_dev_process", id };
285
+ return null;
286
+ }
287
+ // /api/dev-servers and /api/dev-servers/:pid
288
+ if (cleanPath === "/api/dev-servers") {
289
+ if (method === "GET")
290
+ return { route: "scan_dev_servers" };
291
+ return null;
292
+ }
293
+ const devServerMatch = /^\/api\/dev-servers\/([^/]+)$/.exec(cleanPath);
294
+ if (devServerMatch) {
295
+ const id = decodeURIComponent(devServerMatch[1]);
296
+ if (method === "DELETE") {
297
+ const rawPort = query?.get("port");
298
+ const parsedPort = rawPort === null ? Number.NaN : Number(rawPort);
299
+ const port = Number.isFinite(parsedPort) ? parsedPort : undefined;
300
+ return port === undefined
301
+ ? { route: "kill_dev_server", id }
302
+ : { route: "kill_dev_server", id, port };
303
+ }
304
+ return null;
305
+ }
306
+ return null;
307
+ }
308
+ /**
309
+ * Inner switch: route → handler call. Separated so `handleRestRequest`
310
+ * owns ONE try/catch boundary for all error mapping.
311
+ *
312
+ * Meta publish is fired AFTER the handler returns successfully (i.e.
313
+ * after SQLite has committed) so subscribers never see a "create" event
314
+ * for a row the next read won't find. Publish failures are swallowed
315
+ * inside `safePublish` — the wire response goes back regardless.
316
+ *
317
+ * The body is `unknown` from the wire; each handler validates its own
318
+ * shape via the `BadRequestError`-throwing checks they already do.
319
+ */
320
+ export async function dispatchRoute(match, body, deps) {
321
+ const { store, manager, publishMetaEvent, logger } = deps;
322
+ const id = match.id ?? "";
323
+ switch (match.route) {
324
+ case "list_projects": {
325
+ const busyIds = manager.getBusySessionIds();
326
+ const projects = await handleListProjectsAsync(store);
327
+ // Batch all per-project session lookups into a SINGLE query (LEFT JOIN +
328
+ // GROUP BY) instead of calling listSessionsByProject(p.id) in a loop,
329
+ // which issued O(projects) queries each with a per-session correlated
330
+ // COUNT(*) subquery and blocked the event loop past the 30s relay
331
+ // deadline on large projects.
332
+ const sessionsByProject = await store.listSessionsByProjectIdsAsync(projects.map((p) => p.id));
333
+ return projects.map((p) => {
334
+ const sessions = sessionsByProject.get(p.id) ?? [];
335
+ const hasActiveSession = sessions.some((s) => busyIds.has(s.id));
336
+ return { ...p, hasActiveSession, busy: hasActiveSession };
337
+ });
338
+ }
339
+ case "create_project": {
340
+ const project = handleCreateProject(store, asObject(body));
341
+ safePublish(publishMetaEvent, logger, {
342
+ type: "project_created",
343
+ projectId: project.id,
344
+ });
345
+ return project;
346
+ }
347
+ case "update_project": {
348
+ const project = handleUpdateProject(store, id, asObject(body));
349
+ safePublish(publishMetaEvent, logger, {
350
+ type: "project_renamed",
351
+ projectId: project.id,
352
+ });
353
+ return project;
354
+ }
355
+ case "delete_project": {
356
+ // Order matters: tear down live streams BEFORE the row is gone so
357
+ // the bridges don't try to write to a deleted FK target. Mirrors
358
+ // the ordering the deleted Hono route used.
359
+ const project = await store.getProjectAsync(id);
360
+ if (project) {
361
+ const sessions = await store.listSessionsByProjectAsync(id);
362
+ manager.disposeProjectStreams(sessions.map((s) => s.id));
363
+ }
364
+ const result = handleDeleteProject(store, id);
365
+ // Publish AFTER the cascade has committed. We use `id` (the URL
366
+ // param) as the projectId — `handleDeleteProject` would have
367
+ // thrown 404 above if it didn't exist.
368
+ safePublish(publishMetaEvent, logger, {
369
+ type: "project_deleted",
370
+ projectId: id,
371
+ });
372
+ return result;
373
+ }
374
+ case "bind_studio": {
375
+ const result = await handleBindStudioProject(store, id, asObject(body));
376
+ safePublish(publishMetaEvent, logger, {
377
+ type: "project_studio_bound",
378
+ projectId: id,
379
+ studioProjectId: result.studioProjectId,
380
+ studioProjectName: result.studioProjectName,
381
+ });
382
+ // Refresh the X-Project-Id header on active bridges so agent-session
383
+ // credits are attributed to the newly bound project immediately.
384
+ manager.reloadProjectBindings().catch((err) => {
385
+ logger?.error?.(`[dispatcher] reloadProjectBindings failed: ${err instanceof Error ? err.message : String(err)}`);
386
+ });
387
+ // Reload MCP config so the aexol-mcp extension picks up the new binding.
388
+ if (deps.mcpState) {
389
+ handleReloadMcpConfig(deps.mcpState).catch((err) => {
390
+ logger?.error?.(`[dispatcher] MCP reload after bind failed: ${err instanceof Error ? err.message : String(err)}`);
391
+ });
392
+ }
393
+ return result;
394
+ }
395
+ case "unbind_studio": {
396
+ const result = await handleUnbindStudioProject(store, id);
397
+ if (result.studioProjectId) {
398
+ safePublish(publishMetaEvent, logger, {
399
+ type: "project_studio_unbound",
400
+ projectId: id,
401
+ studioProjectId: result.studioProjectId,
402
+ });
403
+ }
404
+ // Clear the X-Project-Id header on active bridges.
405
+ manager.reloadProjectBindings().catch((err) => {
406
+ logger?.error?.(`[dispatcher] reloadProjectBindings failed: ${err instanceof Error ? err.message : String(err)}`);
407
+ });
408
+ // Reload MCP config so the aexol-mcp extension clears the old binding.
409
+ if (deps.mcpState) {
410
+ handleReloadMcpConfig(deps.mcpState).catch((err) => {
411
+ logger?.error?.(`[dispatcher] MCP reload after unbind failed: ${err instanceof Error ? err.message : String(err)}`);
412
+ });
413
+ }
414
+ return result;
415
+ }
416
+ case "get_project_observations": {
417
+ const limitVal = Number(match.query?.get("limit") ?? 30);
418
+ return handleGetProjectObservations(store, id, Math.max(1, Math.min(100, limitVal || 30)));
419
+ }
420
+ case "list_project_sessions": {
421
+ const busyIds = manager.getBusySessionIds();
422
+ const sessions = await handleListSessionsByProjectAsync(store, id);
423
+ return sessions.map((s) => ({
424
+ ...s,
425
+ hasActiveTurn: busyIds.has(s.id),
426
+ busy: busyIds.has(s.id),
427
+ }));
428
+ }
429
+ case "cleanup_project_sessions": {
430
+ const result = handleCleanupProjectSessions(store, id, asObject(body));
431
+ // Tear down any in-flight streams for sessions that are being deleted.
432
+ for (const sessionId of result.ids) {
433
+ manager.disposeSessionStream(sessionId);
434
+ }
435
+ for (const sessionId of result.ids) {
436
+ safePublish(publishMetaEvent, logger, {
437
+ type: "session_deleted",
438
+ projectId: id,
439
+ sessionId,
440
+ });
441
+ }
442
+ return { deleted: result.deleted };
443
+ }
444
+ case "cleanup_all_sessions": {
445
+ const result = handleCleanupAllSessions(store, asObject(body));
446
+ // Tear down any in-flight streams for sessions that are being deleted,
447
+ // across every project.
448
+ for (const sessionId of result.ids) {
449
+ manager.disposeSessionStream(sessionId);
450
+ }
451
+ for (const [sessionId, projectId] of Object.entries(result.projectIds)) {
452
+ safePublish(publishMetaEvent, logger, {
453
+ type: "session_deleted",
454
+ projectId,
455
+ sessionId,
456
+ });
457
+ }
458
+ return { deleted: result.deleted };
459
+ }
460
+ case "create_session": {
461
+ const session = handleCreateSession(store, asObject(body));
462
+ safePublish(publishMetaEvent, logger, {
463
+ type: "session_created",
464
+ projectId: session.projectId,
465
+ sessionId: session.id,
466
+ });
467
+ return session;
468
+ }
469
+ case "get_session": {
470
+ const detail = await handleGetSessionDetailAsync(store, id);
471
+ return {
472
+ ...detail,
473
+ busy: manager.isBusy(id),
474
+ runState: manager.getRunState(id),
475
+ };
476
+ }
477
+ case "get_session_active_agent":
478
+ return await handleGetSessionActiveAgentAsync(store, id, deps.cwd ?? homedir(), deps.agentDir);
479
+ case "update_session_active_agent":
480
+ return await handleUpdateSessionActiveAgentAsync(store, id, asObject(body), manager, deps.cwd ?? homedir(), deps.agentDir);
481
+ case "get_session_messages":
482
+ return await handleGetSessionMessagesAsync(store, id, {
483
+ beforeSeq: match.query?.get("beforeSeq") ?? undefined,
484
+ beforeCreatedAt: match.query?.get("beforeCreatedAt") ?? undefined,
485
+ beforeId: match.query?.get("beforeId") ?? undefined,
486
+ limit: match.query?.get("limit") ?? undefined,
487
+ });
488
+ case "get_session_memory":
489
+ return handleGetSessionMemoryStatus(store, manager, id);
490
+ case "get_session_memory_details":
491
+ return handleGetSessionMemoryDetails(store, manager, id);
492
+ case "update_session": {
493
+ const session = handleUpdateSession(store, id, asObject(body));
494
+ safePublish(publishMetaEvent, logger, {
495
+ type: "session_renamed",
496
+ projectId: session.projectId,
497
+ sessionId: session.id,
498
+ });
499
+ return session;
500
+ }
501
+ case "delete_session": {
502
+ // Capture projectId BEFORE the row is gone — otherwise the
503
+ // post-delete publish has no project to attribute to. Treat a
504
+ // missing session here the same way `handleDeleteSession` does
505
+ // (throws NotFoundError → 404), but capture first.
506
+ const detail = store.getSession(id);
507
+ // Same ordering rationale as delete_project.
508
+ manager.disposeSessionStream(id);
509
+ handleDeleteSession(store, id);
510
+ if (detail) {
511
+ safePublish(publishMetaEvent, logger, {
512
+ type: "session_deleted",
513
+ projectId: detail.projectId,
514
+ sessionId: id,
515
+ });
516
+ }
517
+ return { ok: true };
518
+ }
519
+ case "compact_session":
520
+ return await handleCompactSession(store, manager, id, body);
521
+ case "remember_and_delete_session": {
522
+ // Compact first — the compaction hook's persistProjectObservations
523
+ // runs inside compactSession, writing reflections to cross-session
524
+ // durable memory. On failure (e.g. no API key), we let the error
525
+ // propagate so the session is NOT deleted.
526
+ await handleRememberAndDeleteSession(store, manager, id);
527
+ const detail = store.getSession(id);
528
+ manager.disposeSessionStream(id);
529
+ handleDeleteSession(store, id);
530
+ if (detail) {
531
+ safePublish(publishMetaEvent, logger, {
532
+ type: "session_deleted",
533
+ projectId: detail.projectId,
534
+ sessionId: id,
535
+ });
536
+ }
537
+ return { ok: true };
538
+ }
539
+ case "fork_session": {
540
+ const session = handleForkSession(store, id, asObject(body));
541
+ safePublish(publishMetaEvent, logger, {
542
+ type: "session_created",
543
+ projectId: session.projectId,
544
+ sessionId: session.id,
545
+ });
546
+ return session;
547
+ }
548
+ case "list_path_autocomplete": {
549
+ const prefix = match.query?.get("prefix") ?? "";
550
+ return await handlePathAutocomplete(prefix);
551
+ }
552
+ case "list_directory":
553
+ return await handleListDirectory(asObject(body));
554
+ case "get_home_directory":
555
+ return handleGetHomeDirectory();
556
+ case "search_project_files": {
557
+ const searchQuery = match.query?.get("query") ?? "";
558
+ return handleSearchProjectFiles(store, id, searchQuery);
559
+ }
560
+ case "enqueue_prompt":
561
+ return handleEnqueuePrompt(store, manager, id, asObject(body));
562
+ case "get_prompt_queue":
563
+ return handleGetPromptQueue(store, id);
564
+ case "remove_prompt":
565
+ handleRemovePrompt(store, manager, id, match.itemId ?? "");
566
+ return { ok: true };
567
+ case "clear_prompt_queue":
568
+ handleClearPromptQueue(store, manager, id);
569
+ return { ok: true };
570
+ case "list_mcp_status": {
571
+ if (!deps.mcpState) {
572
+ return [];
573
+ }
574
+ return handleListMcpStatus(deps.mcpState);
575
+ }
576
+ case "reload_mcp_config": {
577
+ return await handleReloadMcpConfig(deps.mcpState);
578
+ }
579
+ case "refresh_native_extensions": {
580
+ if (!deps.refreshNativeExtensions) {
581
+ return { ok: true };
582
+ }
583
+ await deps.refreshNativeExtensions();
584
+ return { ok: true };
585
+ }
586
+ case "refresh_cloud_agents": {
587
+ const teamId = asObject(body).teamId;
588
+ if (typeof teamId !== "string" || teamId.length === 0) {
589
+ throw new BadRequestError("teamId is required");
590
+ }
591
+ if (deps.refreshCloudAgents)
592
+ await deps.refreshCloudAgents(teamId);
593
+ return { ok: true };
594
+ }
595
+ case "wizard_login": {
596
+ return await (deps.startWizardLogin ?? (() => startWizardLogin(deps.cwd ?? homedir())))();
597
+ }
598
+ case "get_settings":
599
+ return await handleGetSettingsAsync(deps.cwd ?? homedir());
600
+ case "put_settings":
601
+ return await handlePutSettingsAsync(deps.cwd ?? homedir(), asObject(body));
602
+ case "get_agent_settings":
603
+ return await handleGetAgentSettingsAsync(deps.cwd ?? homedir(), deps.agentDir);
604
+ case "put_agent_settings":
605
+ return await handlePutAgentSettingsAsync(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
606
+ case "get_prompt_mutation_settings":
607
+ return handleGetPromptMutationSettings(deps.cwd ?? homedir(), deps.agentDir);
608
+ case "put_prompt_mutation_settings":
609
+ return handlePutPromptMutationSettings(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
610
+ case "list_agents": {
611
+ const agentCwd = match.query?.get("cwd") || deps.cwd || homedir();
612
+ const teamId = match.query?.get("teamId") || undefined;
613
+ // Kick off cloud refresh in the background without blocking the response.
614
+ // The previous version awaited refreshCloudAgents (network → backend GraphQL)
615
+ // before returning, so every page refresh stalled the entire REST queue
616
+ // behind this one slow call while 5-7 other rest_requests timed out.
617
+ // Background refresh self-dedupes via CloudAgentCacheEntry.inFlight, so
618
+ // bursts coalesce; stale cache renders immediately and the picker catches
619
+ // up on the next meta_event/live refresh.
620
+ if (teamId && deps.refreshCloudAgents) {
621
+ void deps.refreshCloudAgents(teamId).catch((err) => {
622
+ logger?.error?.(`[dispatcher] cloud agent refresh failed:`, err);
623
+ });
624
+ }
625
+ const agents = handleListAgents(agentCwd, deps.agentDir, teamId);
626
+ return agents;
627
+ }
628
+ case "list_dev_processes":
629
+ return handleListDevProcesses(deps.devProcessRegistry);
630
+ case "start_dev_process":
631
+ return handleStartDevProcess(deps.devProcessRegistry, store, asObject(body), deps.cwd);
632
+ case "kill_dev_process":
633
+ return handleKillDevProcess(deps.devProcessRegistry, id);
634
+ case "restart_dev_process":
635
+ return handleRestartDevProcess(deps.devProcessRegistry, id);
636
+ case "scan_dev_servers":
637
+ return handleScanDevServers(store, deps.devProcessRegistry);
638
+ case "kill_dev_server":
639
+ return await handleKillDetectedDevServer(store, Number(id), match.port);
640
+ }
641
+ }
642
+ /**
643
+ * Invoke `publishMetaEvent` if provided, swallowing any throw. The
644
+ * caller has already returned a 200 to the browser; a publish failure
645
+ * (e.g. relay socket just dropped) must NEVER turn a successful
646
+ * mutation into a wire-level error.
647
+ */
648
+ function safePublish(publish, logger, event) {
649
+ if (!publish)
650
+ return;
651
+ try {
652
+ publish(event);
653
+ }
654
+ catch (err) {
655
+ (logger ?? console).error?.(`[dispatcher] publishMetaEvent failed for ${event.type}:`, err);
656
+ }
657
+ }
658
+ /**
659
+ * Coerce an `unknown` body to a plain object for the handler. Returns an
660
+ * empty object when the body is missing/non-object so the handler's own
661
+ * field checks produce the canonical error message ("name (string) is
662
+ * required") instead of a confusing "body must be object" from us.
663
+ */
664
+ function asObject(body) {
665
+ if (body !== null && typeof body === "object" && !Array.isArray(body)) {
666
+ return body;
667
+ }
668
+ return {};
669
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Wizard login flow (CLI side).
3
+ *
4
+ * Starts the `@aexol/wizard-spectral` login subprocess on behalf of
5
+ * `POST /api/wizard/login` (routed by `relay/routing.ts`). Extracted from
6
+ * `relay/dispatcher.ts` — the dispatcher re-exports `startWizardLogin` and
7
+ * `WizardLoginResult` so existing imports keep working.
8
+ */
9
+ export interface WizardLoginResult {
10
+ status: "configured" | "login_url_observed" | "started" | "completed" | "already_running" | "package_unavailable" | "spawn_failed" | "exited";
11
+ packageName: string;
12
+ message: string;
13
+ loginUrl?: string;
14
+ startedAt?: number;
15
+ }
16
+ export declare function startWizardLogin(directory?: string): Promise<WizardLoginResult>;
17
+ //# sourceMappingURL=wizard-login.d.ts.map