@aexol/spectral 0.9.230 → 0.9.234
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/CHANGELOG.md +2 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +89 -14
- package/dist/backend/generated-images.d.ts +1 -1
- package/dist/backend/generated-images.d.ts.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +12 -1
- package/dist/designer/data/skills/blog-post.d.ts +3 -0
- package/dist/designer/data/skills/blog-post.d.ts.map +1 -0
- package/dist/designer/data/skills/blog-post.js +84 -0
- package/dist/designer/data/skills/blog-post.ts +87 -0
- package/dist/designer/data/skills/critique.d.ts +3 -0
- package/dist/designer/data/skills/critique.d.ts.map +1 -0
- package/dist/designer/data/skills/critique.js +109 -0
- package/dist/designer/data/skills/critique.ts +112 -0
- package/dist/designer/data/skills/dashboard.d.ts +3 -0
- package/dist/designer/data/skills/dashboard.d.ts.map +1 -0
- package/dist/designer/data/skills/dashboard.js +165 -0
- package/dist/designer/data/skills/dashboard.ts +168 -0
- package/dist/designer/data/skills/design-brief.d.ts +3 -0
- package/dist/designer/data/skills/design-brief.d.ts.map +1 -0
- package/dist/designer/data/skills/design-brief.js +141 -0
- package/dist/designer/data/skills/design-brief.ts +144 -0
- package/dist/designer/data/skills/design-skill.d.ts +10 -0
- package/dist/designer/data/skills/design-skill.d.ts.map +1 -0
- package/dist/designer/data/skills/design-skill.js +1 -0
- package/dist/designer/data/skills/design-skill.ts +9 -0
- package/dist/designer/data/skills/docs-page.d.ts +3 -0
- package/dist/designer/data/skills/docs-page.d.ts.map +1 -0
- package/dist/designer/data/skills/docs-page.js +68 -0
- package/dist/designer/data/skills/docs-page.ts +71 -0
- package/dist/designer/data/skills/index.d.ts +3 -0
- package/dist/designer/data/skills/index.d.ts.map +1 -0
- package/dist/designer/data/skills/index.js +28 -0
- package/dist/designer/data/skills/index.ts +30 -0
- package/dist/designer/data/skills/magazine-poster.d.ts +3 -0
- package/dist/designer/data/skills/magazine-poster.d.ts.map +1 -0
- package/dist/designer/data/skills/magazine-poster.js +70 -0
- package/dist/designer/data/skills/magazine-poster.ts +73 -0
- package/dist/designer/data/skills/mobile-app.d.ts +3 -0
- package/dist/designer/data/skills/mobile-app.d.ts.map +1 -0
- package/dist/designer/data/skills/mobile-app.js +229 -0
- package/dist/designer/data/skills/mobile-app.ts +232 -0
- package/dist/designer/data/skills/motion-frames.d.ts +3 -0
- package/dist/designer/data/skills/motion-frames.d.ts.map +1 -0
- package/dist/designer/data/skills/motion-frames.js +121 -0
- package/dist/designer/data/skills/motion-frames.ts +124 -0
- package/dist/designer/data/skills/pricing-page.d.ts +3 -0
- package/dist/designer/data/skills/pricing-page.d.ts.map +1 -0
- package/dist/designer/data/skills/pricing-page.js +76 -0
- package/dist/designer/data/skills/pricing-page.ts +79 -0
- package/dist/designer/data/skills/saas-landing.d.ts +3 -0
- package/dist/designer/data/skills/saas-landing.d.ts.map +1 -0
- package/dist/designer/data/skills/saas-landing.js +71 -0
- package/dist/designer/data/skills/saas-landing.ts +74 -0
- package/dist/designer/data/skills/slide-deck.d.ts +3 -0
- package/dist/designer/data/skills/slide-deck.d.ts.map +1 -0
- package/dist/designer/data/skills/slide-deck.js +213 -0
- package/dist/designer/data/skills/slide-deck.ts +216 -0
- package/dist/designer/data/skills/social-carousel.d.ts +3 -0
- package/dist/designer/data/skills/social-carousel.d.ts.map +1 -0
- package/dist/designer/data/skills/social-carousel.js +135 -0
- package/dist/designer/data/skills/social-carousel.ts +138 -0
- package/dist/designer/data/skills/web-prototype.d.ts +3 -0
- package/dist/designer/data/skills/web-prototype.d.ts.map +1 -0
- package/dist/designer/data/skills/web-prototype.js +159 -0
- package/dist/designer/data/skills/web-prototype.ts +162 -0
- package/dist/designer/skills.d.ts +4 -10
- package/dist/designer/skills.d.ts.map +1 -1
- package/dist/designer/skills.js +2 -1643
- package/dist/extensions/logic-trace.d.ts +1 -1
- package/dist/extensions/logic-trace.d.ts.map +1 -1
- package/dist/extensions/logic-trace.js +199 -70
- package/dist/extensions/video-generation/index.d.ts +3 -0
- package/dist/extensions/video-generation/index.d.ts.map +1 -0
- package/dist/extensions/video-generation/index.js +299 -0
- package/dist/extensions/wizard/index.js +9 -1
- package/dist/generated/parser/ast/nodes.d.ts +115 -0
- package/dist/generated/parser/ast/nodes.d.ts.map +1 -0
- package/dist/generated/parser/ast/nodes.js +8 -0
- package/dist/generated/parser/lexer/lexer.d.ts +28 -0
- package/dist/generated/parser/lexer/lexer.d.ts.map +1 -0
- package/dist/generated/parser/{lexer.js → lexer/lexer.js} +1 -180
- package/dist/generated/parser/{lexer.d.ts → lexer/tokens.d.ts} +5 -24
- package/dist/generated/parser/lexer/tokens.d.ts.map +1 -0
- package/dist/generated/parser/lexer/tokens.js +187 -0
- package/dist/generated/parser/mod.d.ts +5 -7
- package/dist/generated/parser/mod.d.ts.map +1 -1
- package/dist/generated/parser/mod.js +2 -1
- package/dist/generated/parser/parser/cursor.d.ts +47 -0
- package/dist/generated/parser/parser/cursor.d.ts.map +1 -0
- package/dist/generated/parser/parser/cursor.js +12 -0
- package/dist/generated/parser/parser/definitions.d.ts +16 -0
- package/dist/generated/parser/parser/definitions.d.ts.map +1 -0
- package/dist/generated/parser/parser/definitions.js +353 -0
- package/dist/generated/parser/parser/expressions.d.ts +17 -0
- package/dist/generated/parser/parser/expressions.d.ts.map +1 -0
- package/dist/generated/parser/parser/expressions.js +165 -0
- package/dist/generated/parser/parser.d.ts +18 -133
- package/dist/generated/parser/parser.d.ts.map +1 -1
- package/dist/generated/parser/parser.js +9 -505
- package/dist/generated/parser/tokenizer.d.ts +1 -2
- package/dist/generated/parser/tokenizer.d.ts.map +1 -1
- package/dist/generated/parser/tokenizer.js +2 -1
- package/dist/generated/parser/validator.d.ts +1 -1
- package/dist/generated/parser/validator.d.ts.map +1 -1
- package/dist/generated/zeus/const.d.ts.map +1 -1
- package/dist/generated/zeus/const.js +30 -0
- package/dist/generated/zeus/index.d.ts +140 -0
- package/dist/generated/zeus/index.d.ts.map +1 -1
- package/dist/generated/zeus/index.js +5 -0
- package/dist/mcp/glimpse-ui.js +9 -1
- package/dist/relay/dispatcher.d.ts +23 -104
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +21 -1130
- package/dist/relay/rest.d.ts +31 -0
- package/dist/relay/rest.d.ts.map +1 -0
- package/dist/relay/rest.js +85 -0
- package/dist/relay/routing.d.ts +109 -0
- package/dist/relay/routing.d.ts.map +1 -0
- package/dist/relay/routing.js +669 -0
- package/dist/relay/wizard-login.d.ts +17 -0
- package/dist/relay/wizard-login.d.ts.map +1 -0
- package/dist/relay/wizard-login.js +419 -0
- package/dist/sdk/agent-core/agent-loop.d.ts +2 -9
- package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent-loop.js +17 -291
- package/dist/sdk/agent-core/agent.d.ts +11 -4
- package/dist/sdk/agent-core/agent.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent.js +16 -4
- package/dist/sdk/agent-core/tool-call-registry.d.ts +75 -0
- package/dist/sdk/agent-core/tool-call-registry.d.ts.map +1 -0
- package/dist/sdk/agent-core/tool-call-registry.js +131 -0
- package/dist/sdk/agent-core/tool-execution.d.ts +33 -0
- package/dist/sdk/agent-core/tool-execution.d.ts.map +1 -0
- package/dist/sdk/agent-core/tool-execution.js +399 -0
- package/dist/sdk/agent-core/types.d.ts +15 -4
- package/dist/sdk/agent-core/types.d.ts.map +1 -1
- package/dist/sdk/ai/env-api-keys.js +9 -1
- package/dist/sdk/ai/index.d.ts +3 -0
- package/dist/sdk/ai/index.d.ts.map +1 -1
- package/dist/sdk/ai/index.js +3 -0
- package/dist/sdk/ai/providers/faux.d.ts.map +1 -1
- package/dist/sdk/ai/providers/faux.js +3 -0
- package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts +39 -6
- package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts.map +1 -1
- package/dist/sdk/ai/providers/leaked-tool-call-parser.js +159 -18
- package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts +48 -0
- package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts.map +1 -0
- package/dist/sdk/ai/providers/openai/reasoning-replay.js +209 -0
- package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.js +23 -211
- package/dist/sdk/ai/providers/openrouter-videos.d.ts +3 -0
- package/dist/sdk/ai/providers/openrouter-videos.d.ts.map +1 -0
- package/dist/sdk/ai/providers/openrouter-videos.js +412 -0
- package/dist/sdk/ai/providers/register-builtins.d.ts.map +1 -1
- package/dist/sdk/ai/providers/register-builtins.js +6 -0
- package/dist/sdk/ai/providers/simple-options.d.ts +1 -6
- package/dist/sdk/ai/providers/simple-options.d.ts.map +1 -1
- package/dist/sdk/ai/providers/simple-options.js +0 -22
- package/dist/sdk/ai/providers/transform-messages.d.ts.map +1 -1
- package/dist/sdk/ai/providers/transform-messages.js +52 -1
- package/dist/sdk/ai/types.d.ts +91 -10
- package/dist/sdk/ai/types.d.ts.map +1 -1
- package/dist/sdk/ai/video-models.d.ts +10 -0
- package/dist/sdk/ai/video-models.d.ts.map +1 -0
- package/dist/sdk/ai/video-models.generated.d.ts +440 -0
- package/dist/sdk/ai/video-models.generated.d.ts.map +1 -0
- package/dist/sdk/ai/video-models.generated.js +441 -0
- package/dist/sdk/ai/video-models.js +22 -0
- package/dist/sdk/ai/videos-api-registry.d.ts +14 -0
- package/dist/sdk/ai/videos-api-registry.d.ts.map +1 -0
- package/dist/sdk/ai/videos-api-registry.js +21 -0
- package/dist/sdk/ai/videos.d.ts +4 -0
- package/dist/sdk/ai/videos.d.ts.map +1 -0
- package/dist/sdk/ai/videos.js +13 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts +8 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +11 -0
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +6 -118
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/compaction.js +11 -733
- package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts +58 -0
- package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/cut-points.js +191 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts +28 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.js +156 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.d.ts +21 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.js +181 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.d.ts +47 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.js +224 -0
- package/dist/sdk/coding-agent/core/extensions/loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/loader.js +14 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts +26 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +23 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts +157 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.js +13 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +10 -367
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/types.js +2 -10
- package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts +219 -0
- package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/extensions/ui-types.js +4 -0
- package/dist/sdk/coding-agent/core/messages.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/messages.js +42 -2
- package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts +115 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.js +1184 -0
- package/dist/sdk/coding-agent/core/package/discovery.d.ts +20 -0
- package/dist/sdk/coding-agent/core/package/discovery.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/discovery.js +283 -0
- package/dist/sdk/coding-agent/core/package/ignore.d.ts +29 -0
- package/dist/sdk/coding-agent/core/package/ignore.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/ignore.js +235 -0
- package/dist/sdk/coding-agent/core/package/types.d.ts +128 -0
- package/dist/sdk/coding-agent/core/package/types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/types.js +18 -0
- package/dist/sdk/coding-agent/core/package/update-check.d.ts +43 -0
- package/dist/sdk/coding-agent/core/package/update-check.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/update-check.js +253 -0
- package/dist/sdk/coding-agent/core/package-manager.d.ts +2 -188
- package/dist/sdk/coding-agent/core/package-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/package-manager.js +1 -1940
- package/dist/sdk/coding-agent/core/resource-loader.d.ts +11 -0
- package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/resource-loader.js +23 -6
- package/dist/sdk/coding-agent/core/sdk.d.ts +24 -2
- package/dist/sdk/coding-agent/core/sdk.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/sdk.js +15 -3
- package/dist/sdk/coding-agent/core/session/migrate.d.ts +18 -0
- package/dist/sdk/coding-agent/core/session/migrate.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/session/migrate.js +78 -0
- package/dist/sdk/coding-agent/core/session/types.d.ts +165 -0
- package/dist/sdk/coding-agent/core/session/types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/session/types.js +4 -0
- package/dist/sdk/coding-agent/core/session-manager.d.ts +3 -161
- package/dist/sdk/coding-agent/core/session-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/session-manager.js +4 -72
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +0 -8
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +3 -3
- package/dist/sdk/coding-agent/index.d.ts +2 -2
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/index.js +1 -1
- package/dist/sdk-public.d.ts +96 -0
- package/dist/sdk-public.d.ts.map +1 -0
- package/dist/sdk-public.js +109 -0
- package/dist/server/agent-bridge/rehydrate.d.ts +2 -2
- package/dist/server/agent-bridge/rehydrate.d.ts.map +1 -1
- package/dist/server/agent-bridge/rehydrate.js +14 -0
- package/dist/server/agent-bridge.d.ts +8 -2
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +25 -2
- package/dist/server/session-stream.d.ts +19 -0
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +35 -0
- package/dist/server/wire.d.ts +13 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +8 -2
- package/dist/generated/parser/lexer.d.ts.map +0 -1
package/dist/relay/dispatcher.js
CHANGED
|
@@ -37,1145 +37,25 @@
|
|
|
37
37
|
* - Backend cancels pending REST requests with `code:"timeout"` (30 s)
|
|
38
38
|
* or `code:"machine_offline"` (CLI socket dropped). Both are
|
|
39
39
|
* transparent to this layer — we just respond when we can.
|
|
40
|
+
*
|
|
41
|
+
* Path matching + the REST request handler + the wizard login flow live in
|
|
42
|
+
* sibling modules (`routing.ts`, `rest.ts`, `wizard-login.ts`) and are
|
|
43
|
+
* re-exported below so existing imports from `relay/dispatcher.js` keep
|
|
44
|
+
* working.
|
|
40
45
|
*/
|
|
41
|
-
import { spawn } from "node:child_process";
|
|
42
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
43
|
-
import { createRequire } from "node:module";
|
|
44
|
-
import { homedir } from "node:os";
|
|
45
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
46
|
-
import { BadRequestError, NotFoundError } from "../server/handlers/errors.js";
|
|
47
|
-
import { handleListMcpStatus } from "../server/handlers/mcp-status.js";
|
|
48
|
-
import { handleReloadMcpConfig } from "../server/handlers/mcp-reload.js";
|
|
49
|
-
import { handlePathAutocomplete } from "../server/handlers/paths-autocomplete.js";
|
|
50
|
-
import { handleGetHomeDirectory } from "../server/handlers/paths-home.js";
|
|
51
|
-
import { handleListDirectory } from "../server/handlers/paths-list.js";
|
|
52
|
-
import { handleSearchProjectFiles } from "../server/handlers/files-search.js";
|
|
53
|
-
import { handleBindStudioProject, handleUnbindStudioProject, handleCleanupProjectSessions, handleCreateProject, handleDeleteProject, handleGetProjectObservations, handleListProjectsAsync, handleListSessionsByProjectAsync, handleUpdateProject, } from "../server/handlers/projects.js";
|
|
54
|
-
import { handleCompactSession, handleCleanupAllSessions, handleCreateSession, handleDeleteSession, handleForkSession, handleRememberAndDeleteSession, handleGetSessionDetailAsync, handleGetSessionMessagesAsync, handleGetSessionMemoryDetails, handleGetSessionMemoryStatus, handleUpdateSession, handleGetSessionActiveAgentAsync, handleUpdateSessionActiveAgentAsync, } from "../server/handlers/sessions.js";
|
|
55
|
-
import { handleClearPromptQueue, handleEnqueuePrompt, handleGetPromptQueue, handleRemovePrompt, } from "../server/handlers/queue.js";
|
|
56
|
-
import { handleGetAgentSettingsAsync, handleListAgents, handlePutAgentSettingsAsync, } from "../server/handlers/agent-settings.js";
|
|
57
|
-
import { handleGetSettingsAsync, handlePutSettingsAsync, } from "../server/handlers/settings.js";
|
|
58
|
-
import { handleGetPromptMutationSettings, handlePutPromptMutationSettings, } from "../server/handlers/prompt-mutation-settings.js";
|
|
59
46
|
import { shutdownState } from "../server/shutdown.js";
|
|
60
47
|
import { coerceImages, hasImages, MAX_INLINE_IMAGE_BASE64_CHARS, } from "../server/image-attachments.js";
|
|
61
48
|
import { humanizeProviderError } from "../server/error-humanizer.js";
|
|
62
|
-
import { handleKillDevProcess, handleKillDetectedDevServer, handleListDevProcesses, handleRestartDevProcess, handleScanDevServers, handleStartDevProcess, } from "../server/handlers/dev-processes.js";
|
|
63
49
|
import { handleAutoResearch } from "./auto-research.js";
|
|
64
50
|
import { handleAutoOptimize } from "./auto-optimizer.js";
|
|
65
51
|
import { chunkHistory } from "./history-chunker.js";
|
|
66
52
|
import { gzipEncodeMessages, shouldCompressHistory } from "./history-compression.js";
|
|
67
53
|
import { projectHistoryForReplay } from "./history-projection.js";
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* also marginally slower and harder to read for ~11 routes.
|
|
74
|
-
*/
|
|
75
|
-
export function matchRoute(method, path) {
|
|
76
|
-
// Strip query string for path matching but keep it for the handler.
|
|
77
|
-
const qIdx = path.indexOf("?");
|
|
78
|
-
const cleanPath = qIdx === -1 ? path : path.slice(0, qIdx);
|
|
79
|
-
const query = qIdx >= 0 ? new URLSearchParams(path.slice(qIdx + 1)) : undefined;
|
|
80
|
-
// /api/mcp-status
|
|
81
|
-
if (cleanPath === "/api/mcp-status") {
|
|
82
|
-
if (method === "GET")
|
|
83
|
-
return { route: "list_mcp_status" };
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
if (cleanPath === "/api/mcp-reload") {
|
|
87
|
-
if (method === "POST")
|
|
88
|
-
return { route: "reload_mcp_config" };
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
if (cleanPath === "/api/native-extensions/refresh") {
|
|
92
|
-
if (method === "POST")
|
|
93
|
-
return { route: "refresh_native_extensions" };
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
if (cleanPath === "/api/cloud-agents/refresh") {
|
|
97
|
-
if (method === "POST")
|
|
98
|
-
return { route: "refresh_cloud_agents" };
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
if (cleanPath === "/api/wizard/login") {
|
|
102
|
-
if (method === "POST")
|
|
103
|
-
return { route: "wizard_login" };
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
// /api/settings
|
|
107
|
-
if (cleanPath === "/api/settings") {
|
|
108
|
-
if (method === "GET")
|
|
109
|
-
return { route: "get_settings" };
|
|
110
|
-
if (method === "PUT")
|
|
111
|
-
return { route: "put_settings" };
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
// /api/agent-settings
|
|
115
|
-
if (cleanPath === "/api/agent-settings") {
|
|
116
|
-
if (method === "GET")
|
|
117
|
-
return { route: "get_agent_settings" };
|
|
118
|
-
if (method === "PUT")
|
|
119
|
-
return { route: "put_agent_settings" };
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
if (cleanPath === "/api/prompt-mutation-settings") {
|
|
123
|
-
if (method === "GET")
|
|
124
|
-
return { route: "get_prompt_mutation_settings" };
|
|
125
|
-
if (method === "PUT")
|
|
126
|
-
return { route: "put_prompt_mutation_settings" };
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
// /api/agents
|
|
130
|
-
if (cleanPath === "/api/agents") {
|
|
131
|
-
if (method === "GET")
|
|
132
|
-
return { route: "list_agents", query };
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
// /api/paths/autocomplete
|
|
136
|
-
if (cleanPath === "/api/paths/autocomplete") {
|
|
137
|
-
if (method === "GET")
|
|
138
|
-
return { route: "list_path_autocomplete", query };
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
// /api/paths/list
|
|
142
|
-
if (cleanPath === "/api/paths/list") {
|
|
143
|
-
if (method === "POST")
|
|
144
|
-
return { route: "list_directory" };
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
// /api/paths/home
|
|
148
|
-
if (cleanPath === "/api/paths/home") {
|
|
149
|
-
if (method === "POST")
|
|
150
|
-
return { route: "get_home_directory" };
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
// /api/projects
|
|
154
|
-
if (cleanPath === "/api/projects") {
|
|
155
|
-
if (method === "GET")
|
|
156
|
-
return { route: "list_projects" };
|
|
157
|
-
if (method === "POST")
|
|
158
|
-
return { route: "create_project" };
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
// /api/sessions
|
|
162
|
-
if (cleanPath === "/api/sessions") {
|
|
163
|
-
if (method === "POST")
|
|
164
|
-
return { route: "create_session" };
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
// /api/projects/:id/files/search
|
|
168
|
-
const projectFilesMatch = /^\/api\/projects\/([^/]+)\/files\/search$/.exec(cleanPath);
|
|
169
|
-
if (projectFilesMatch) {
|
|
170
|
-
const id = decodeURIComponent(projectFilesMatch[1]);
|
|
171
|
-
if (method === "GET")
|
|
172
|
-
return { route: "search_project_files", id, query };
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
// /api/projects/:id/bind-studio and /api/projects/:id/unbind-studio
|
|
176
|
-
const bindStudioMatch = /^\/api\/projects\/([^/]+)\/bind-studio$/.exec(cleanPath);
|
|
177
|
-
if (bindStudioMatch) {
|
|
178
|
-
const id = decodeURIComponent(bindStudioMatch[1]);
|
|
179
|
-
if (method === "POST")
|
|
180
|
-
return { route: "bind_studio", id };
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
const unbindStudioMatch = /^\/api\/projects\/([^/]+)\/unbind-studio$/.exec(cleanPath);
|
|
184
|
-
if (unbindStudioMatch) {
|
|
185
|
-
const id = decodeURIComponent(unbindStudioMatch[1]);
|
|
186
|
-
if (method === "POST")
|
|
187
|
-
return { route: "unbind_studio", id };
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
// /api/projects/:id/observations
|
|
191
|
-
const projectObsMatch = /^\/api\/projects\/([^/]+)\/observations$/.exec(cleanPath);
|
|
192
|
-
if (projectObsMatch) {
|
|
193
|
-
const id = decodeURIComponent(projectObsMatch[1]);
|
|
194
|
-
if (method === "GET")
|
|
195
|
-
return { route: "get_project_observations", id };
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
// /api/projects/:id and /api/projects/:id/sessions
|
|
199
|
-
const projectMatch = /^\/api\/projects\/([^/]+)(\/sessions)?$/.exec(cleanPath);
|
|
200
|
-
if (projectMatch) {
|
|
201
|
-
const id = decodeURIComponent(projectMatch[1]);
|
|
202
|
-
const isSessions = projectMatch[2] === "/sessions";
|
|
203
|
-
if (isSessions) {
|
|
204
|
-
if (method === "GET")
|
|
205
|
-
return { route: "list_project_sessions", id };
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
if (method === "PATCH")
|
|
209
|
-
return { route: "update_project", id };
|
|
210
|
-
if (method === "DELETE")
|
|
211
|
-
return { route: "delete_project", id };
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
// /api/projects/:id/cleanup-sessions
|
|
215
|
-
const projectCleanupMatch = /^\/api\/projects\/([^/]+)\/cleanup-sessions$/.exec(cleanPath);
|
|
216
|
-
if (projectCleanupMatch) {
|
|
217
|
-
const id = decodeURIComponent(projectCleanupMatch[1]);
|
|
218
|
-
if (method === "POST")
|
|
219
|
-
return { route: "cleanup_project_sessions", id };
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
// /api/sessions/cleanup (must precede the /api/sessions/:id matches —
|
|
223
|
-
// "cleanup" would otherwise be captured as an :id)
|
|
224
|
-
if (cleanPath === "/api/sessions/cleanup") {
|
|
225
|
-
if (method === "POST")
|
|
226
|
-
return { route: "cleanup_all_sessions" };
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
// /api/sessions/:id
|
|
230
|
-
const sessionMessagesMatch = /^\/api\/sessions\/([^/]+)\/messages$/.exec(cleanPath);
|
|
231
|
-
if (sessionMessagesMatch) {
|
|
232
|
-
const id = decodeURIComponent(sessionMessagesMatch[1]);
|
|
233
|
-
if (method === "GET")
|
|
234
|
-
return { route: "get_session_messages", id, query };
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
const sessionMatch = /^\/api\/sessions\/([^/]+)$/.exec(cleanPath);
|
|
238
|
-
if (sessionMatch) {
|
|
239
|
-
const id = decodeURIComponent(sessionMatch[1]);
|
|
240
|
-
if (method === "GET")
|
|
241
|
-
return { route: "get_session", id };
|
|
242
|
-
if (method === "PATCH")
|
|
243
|
-
return { route: "update_session", id };
|
|
244
|
-
if (method === "DELETE")
|
|
245
|
-
return { route: "delete_session", id };
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
const sessionAgentMatch = /^\/api\/sessions\/([^/]+)\/active-agent$/.exec(cleanPath);
|
|
249
|
-
if (sessionAgentMatch) {
|
|
250
|
-
const id = decodeURIComponent(sessionAgentMatch[1]);
|
|
251
|
-
if (method === "GET")
|
|
252
|
-
return { route: "get_session_active_agent", id };
|
|
253
|
-
if (method === "PUT")
|
|
254
|
-
return { route: "update_session_active_agent", id };
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
// /api/sessions/:id/memory/details and /api/sessions/:id/memory and /api/sessions/:id/compact
|
|
258
|
-
const sessionActionMatch = /^\/api\/sessions\/([^/]+)\/(memory(?:\/details)?|compact)$/.exec(cleanPath);
|
|
259
|
-
if (sessionActionMatch) {
|
|
260
|
-
const id = decodeURIComponent(sessionActionMatch[1]);
|
|
261
|
-
const action = sessionActionMatch[2];
|
|
262
|
-
if (action === "memory" && method === "GET")
|
|
263
|
-
return { route: "get_session_memory", id };
|
|
264
|
-
if (action === "memory/details" && method === "GET")
|
|
265
|
-
return { route: "get_session_memory_details", id };
|
|
266
|
-
if (action === "compact" && method === "POST")
|
|
267
|
-
return { route: "compact_session", id };
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
// /api/sessions/:id/remember-and-delete
|
|
271
|
-
const rememberDeleteMatch = /^\/api\/sessions\/([^/]+)\/remember-and-delete$/.exec(cleanPath);
|
|
272
|
-
if (rememberDeleteMatch) {
|
|
273
|
-
const id = decodeURIComponent(rememberDeleteMatch[1]);
|
|
274
|
-
if (method === "POST")
|
|
275
|
-
return { route: "remember_and_delete_session", id };
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
// /api/sessions/:id/fork
|
|
279
|
-
const forkMatch = /^\/api\/sessions\/([^/]+)\/fork$/.exec(cleanPath);
|
|
280
|
-
if (forkMatch) {
|
|
281
|
-
const id = decodeURIComponent(forkMatch[1]);
|
|
282
|
-
if (method === "POST")
|
|
283
|
-
return { route: "fork_session", id };
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
// /api/sessions/:id/queue and /api/sessions/:id/queue/:itemId
|
|
287
|
-
const queueMatch = /^\/api\/sessions\/([^/]+)\/queue(\/([^/]+))?$/.exec(cleanPath);
|
|
288
|
-
if (queueMatch) {
|
|
289
|
-
const id = decodeURIComponent(queueMatch[1]);
|
|
290
|
-
const itemId = queueMatch[3] ? decodeURIComponent(queueMatch[3]) : undefined;
|
|
291
|
-
if (!itemId) {
|
|
292
|
-
if (method === "GET")
|
|
293
|
-
return { route: "get_prompt_queue", id };
|
|
294
|
-
if (method === "POST")
|
|
295
|
-
return { route: "enqueue_prompt", id };
|
|
296
|
-
if (method === "DELETE")
|
|
297
|
-
return { route: "clear_prompt_queue", id };
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
if (method === "DELETE")
|
|
301
|
-
return { route: "remove_prompt", id, itemId };
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
// /api/dev-processes and /api/dev-processes/:id
|
|
306
|
-
if (cleanPath === "/api/dev-processes") {
|
|
307
|
-
if (method === "GET")
|
|
308
|
-
return { route: "list_dev_processes" };
|
|
309
|
-
if (method === "POST")
|
|
310
|
-
return { route: "start_dev_process" };
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
const restartDevProcessMatch = /^\/api\/dev-processes\/([^/]+)\/restart$/.exec(cleanPath);
|
|
314
|
-
if (restartDevProcessMatch) {
|
|
315
|
-
const id = decodeURIComponent(restartDevProcessMatch[1]);
|
|
316
|
-
if (method === "POST")
|
|
317
|
-
return { route: "restart_dev_process", id };
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
const devProcessMatch = /^\/api\/dev-processes\/([^/]+)$/.exec(cleanPath);
|
|
321
|
-
if (devProcessMatch) {
|
|
322
|
-
const id = decodeURIComponent(devProcessMatch[1]);
|
|
323
|
-
if (method === "DELETE")
|
|
324
|
-
return { route: "kill_dev_process", id };
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
// /api/dev-servers and /api/dev-servers/:pid
|
|
328
|
-
if (cleanPath === "/api/dev-servers") {
|
|
329
|
-
if (method === "GET")
|
|
330
|
-
return { route: "scan_dev_servers" };
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
const devServerMatch = /^\/api\/dev-servers\/([^/]+)$/.exec(cleanPath);
|
|
334
|
-
if (devServerMatch) {
|
|
335
|
-
const id = decodeURIComponent(devServerMatch[1]);
|
|
336
|
-
if (method === "DELETE") {
|
|
337
|
-
const rawPort = query?.get("port");
|
|
338
|
-
const parsedPort = rawPort === null ? Number.NaN : Number(rawPort);
|
|
339
|
-
const port = Number.isFinite(parsedPort) ? parsedPort : undefined;
|
|
340
|
-
return port === undefined
|
|
341
|
-
? { route: "kill_dev_server", id }
|
|
342
|
-
: { route: "kill_dev_server", id, port };
|
|
343
|
-
}
|
|
344
|
-
return null;
|
|
345
|
-
}
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
const WIZARD_PACKAGE_NAME = "@aexol/wizard-spectral";
|
|
349
|
-
const WIZARD_CLI_BIN_NAME = "spectral-wizard";
|
|
350
|
-
const WIZARD_LOGIN_STARTUP_TIMEOUT_MS = 5_000;
|
|
351
|
-
const requireForWizard = createRequire(import.meta.url);
|
|
352
|
-
let activeWizardLoginRun = null;
|
|
353
|
-
let activeWizardLoginStartPromise = null;
|
|
354
|
-
function getActiveWizardLoginRun() {
|
|
355
|
-
const run = activeWizardLoginRun;
|
|
356
|
-
if (!run)
|
|
357
|
-
return null;
|
|
358
|
-
if (run.child.exitCode !== null || run.child.killed) {
|
|
359
|
-
activeWizardLoginRun = null;
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
return run;
|
|
363
|
-
}
|
|
364
|
-
function toSafeErrorMessage(error) {
|
|
365
|
-
if (error instanceof Error)
|
|
366
|
-
return error.message;
|
|
367
|
-
if (typeof error === "string")
|
|
368
|
-
return error;
|
|
369
|
-
return "Unknown error";
|
|
370
|
-
}
|
|
371
|
-
function asRecord(value) {
|
|
372
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
return value;
|
|
376
|
-
}
|
|
377
|
-
function findWizardPackageJsonFromEntry(entryPath) {
|
|
378
|
-
let current = dirname(entryPath);
|
|
379
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
380
|
-
const candidate = join(current, "package.json");
|
|
381
|
-
if (existsSync(candidate)) {
|
|
382
|
-
try {
|
|
383
|
-
const packageJson = JSON.parse(readFileSync(candidate, "utf8"));
|
|
384
|
-
if (asRecord(packageJson)?.name === WIZARD_PACKAGE_NAME)
|
|
385
|
-
return candidate;
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
// Keep walking upward; malformed metadata is handled by the final reader.
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const parent = dirname(current);
|
|
392
|
-
if (parent === current)
|
|
393
|
-
break;
|
|
394
|
-
current = parent;
|
|
395
|
-
}
|
|
396
|
-
return undefined;
|
|
397
|
-
}
|
|
398
|
-
function resolveWizardPackageJsonPath() {
|
|
399
|
-
try {
|
|
400
|
-
return { ok: true, path: requireForWizard.resolve(`${WIZARD_PACKAGE_NAME}/package.json`) };
|
|
401
|
-
}
|
|
402
|
-
catch (error) {
|
|
403
|
-
try {
|
|
404
|
-
const entryPath = requireForWizard.resolve(WIZARD_PACKAGE_NAME);
|
|
405
|
-
const packageJsonPath = findWizardPackageJsonFromEntry(entryPath);
|
|
406
|
-
if (packageJsonPath)
|
|
407
|
-
return { ok: true, path: packageJsonPath };
|
|
408
|
-
}
|
|
409
|
-
catch {
|
|
410
|
-
// Fall through to the original package-unavailable message.
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
ok: false,
|
|
414
|
-
message: `${WIZARD_PACKAGE_NAME} is not available on this machine: ${toSafeErrorMessage(error)}`,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
function readWizardPackageJson(packageJsonPath) {
|
|
419
|
-
try {
|
|
420
|
-
return { ok: true, json: JSON.parse(readFileSync(packageJsonPath, "utf8")) };
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
return {
|
|
424
|
-
ok: false,
|
|
425
|
-
message: `Could not read ${WIZARD_PACKAGE_NAME} package metadata: ${toSafeErrorMessage(error)}`,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
function getWizardBinRelativePath(packageJson) {
|
|
430
|
-
const pkg = asRecord(packageJson);
|
|
431
|
-
const bin = pkg?.bin;
|
|
432
|
-
if (typeof bin === "string" && bin.trim())
|
|
433
|
-
return bin;
|
|
434
|
-
const binRecord = asRecord(bin);
|
|
435
|
-
const namedBin = binRecord?.[WIZARD_CLI_BIN_NAME];
|
|
436
|
-
return typeof namedBin === "string" && namedBin.trim() ? namedBin : undefined;
|
|
437
|
-
}
|
|
438
|
-
function findWindowsBinShim(packageRoot) {
|
|
439
|
-
let current = packageRoot;
|
|
440
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
441
|
-
const candidate = join(current, "node_modules", ".bin", `${WIZARD_CLI_BIN_NAME}.cmd`);
|
|
442
|
-
if (existsSync(candidate))
|
|
443
|
-
return candidate;
|
|
444
|
-
const parent = dirname(current);
|
|
445
|
-
if (parent === current)
|
|
446
|
-
break;
|
|
447
|
-
current = parent;
|
|
448
|
-
}
|
|
449
|
-
return undefined;
|
|
450
|
-
}
|
|
451
|
-
function resolveWizardBinCommand() {
|
|
452
|
-
const packageJsonPath = resolveWizardPackageJsonPath();
|
|
453
|
-
if (!packageJsonPath.ok)
|
|
454
|
-
return packageJsonPath;
|
|
455
|
-
const packageJson = readWizardPackageJson(packageJsonPath.path);
|
|
456
|
-
if (!packageJson.ok)
|
|
457
|
-
return packageJson;
|
|
458
|
-
const binRelativePath = getWizardBinRelativePath(packageJson.json);
|
|
459
|
-
if (!binRelativePath) {
|
|
460
|
-
return {
|
|
461
|
-
ok: false,
|
|
462
|
-
message: `${WIZARD_PACKAGE_NAME} is installed but does not expose a ${WIZARD_CLI_BIN_NAME} bin.`,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
const packageRoot = dirname(packageJsonPath.path);
|
|
466
|
-
const packageBinPath = isAbsolute(binRelativePath)
|
|
467
|
-
? binRelativePath
|
|
468
|
-
: join(packageRoot, binRelativePath);
|
|
469
|
-
if (process.platform === "win32") {
|
|
470
|
-
const cmdShim = findWindowsBinShim(packageRoot);
|
|
471
|
-
if (cmdShim) {
|
|
472
|
-
return {
|
|
473
|
-
ok: true,
|
|
474
|
-
bin: { command: cmdShim, argsPrefix: [] },
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return {
|
|
479
|
-
ok: true,
|
|
480
|
-
bin: { command: packageBinPath, argsPrefix: [] },
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function collectWizardTools(value) {
|
|
484
|
-
if (Array.isArray(value))
|
|
485
|
-
return value;
|
|
486
|
-
const record = asRecord(value);
|
|
487
|
-
const tools = record?.tools;
|
|
488
|
-
return Array.isArray(tools) ? tools : [];
|
|
489
|
-
}
|
|
490
|
-
function findWizardStatusTool(tools) {
|
|
491
|
-
for (const tool of tools) {
|
|
492
|
-
const record = asRecord(tool);
|
|
493
|
-
if (record?.name !== "wizard_status" || typeof record.execute !== "function") {
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
return {
|
|
497
|
-
execute: record.execute,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
return null;
|
|
501
|
-
}
|
|
502
|
-
function normalizeWizardAuthStatus(value) {
|
|
503
|
-
const normalized = value.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
504
|
-
if (normalized === "configured" ||
|
|
505
|
-
normalized === "authenticated" ||
|
|
506
|
-
normalized === "ready" ||
|
|
507
|
-
normalized === "ok" ||
|
|
508
|
-
normalized === "connected") {
|
|
509
|
-
return "configured";
|
|
510
|
-
}
|
|
511
|
-
if (normalized === "missing_auth" ||
|
|
512
|
-
normalized === "auth_missing" ||
|
|
513
|
-
normalized === "needs_auth" ||
|
|
514
|
-
normalized === "not_authenticated" ||
|
|
515
|
-
normalized === "unauthenticated" ||
|
|
516
|
-
normalized === "not_configured") {
|
|
517
|
-
return "missing_auth";
|
|
518
|
-
}
|
|
519
|
-
return "unknown";
|
|
520
|
-
}
|
|
521
|
-
function parseWizardAuthStatusFromObject(record) {
|
|
522
|
-
for (const key of ["status", "authStatus", "state"]) {
|
|
523
|
-
const raw = record[key];
|
|
524
|
-
if (typeof raw !== "string")
|
|
525
|
-
continue;
|
|
526
|
-
const status = normalizeWizardAuthStatus(raw);
|
|
527
|
-
if (status !== "unknown")
|
|
528
|
-
return status;
|
|
529
|
-
}
|
|
530
|
-
for (const key of ["configured", "authenticated", "isAuthenticated", "hasAuth"]) {
|
|
531
|
-
const raw = record[key];
|
|
532
|
-
if (typeof raw === "boolean")
|
|
533
|
-
return raw ? "configured" : "missing_auth";
|
|
534
|
-
}
|
|
535
|
-
return "unknown";
|
|
536
|
-
}
|
|
537
|
-
function parseWizardAuthStatus(statusOutput) {
|
|
538
|
-
const candidates = [statusOutput];
|
|
539
|
-
const outputRecord = asRecord(statusOutput);
|
|
540
|
-
if (outputRecord) {
|
|
541
|
-
candidates.push(outputRecord.details, outputRecord.data, outputRecord.result);
|
|
542
|
-
}
|
|
543
|
-
for (const candidate of candidates) {
|
|
544
|
-
const record = asRecord(candidate);
|
|
545
|
-
if (!record)
|
|
546
|
-
continue;
|
|
547
|
-
const status = parseWizardAuthStatusFromObject(record);
|
|
548
|
-
if (status !== "unknown")
|
|
549
|
-
return status;
|
|
550
|
-
}
|
|
551
|
-
return "unknown";
|
|
552
|
-
}
|
|
553
|
-
async function checkWizardAuthStatus(directory) {
|
|
554
|
-
let importedModule;
|
|
555
|
-
try {
|
|
556
|
-
importedModule = await import(WIZARD_PACKAGE_NAME);
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
return {
|
|
560
|
-
status: "package_unavailable",
|
|
561
|
-
message: `${WIZARD_PACKAGE_NAME} is not available on this machine: ${toSafeErrorMessage(error)}`,
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
const buildSpectralTools = asRecord(importedModule)?.buildSpectralTools;
|
|
565
|
-
if (typeof buildSpectralTools !== "function") {
|
|
566
|
-
return {
|
|
567
|
-
status: "unknown",
|
|
568
|
-
message: "Wizard status API is unavailable; starting login.",
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
try {
|
|
572
|
-
const tools = collectWizardTools(await buildSpectralTools());
|
|
573
|
-
const statusTool = findWizardStatusTool(tools);
|
|
574
|
-
if (!statusTool) {
|
|
575
|
-
return {
|
|
576
|
-
status: "unknown",
|
|
577
|
-
message: "Wizard status tool is unavailable; starting login.",
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
const status = parseWizardAuthStatus(await statusTool.execute("wizard-ui-login-status", { directory }));
|
|
581
|
-
return {
|
|
582
|
-
status,
|
|
583
|
-
message: status === "configured"
|
|
584
|
-
? "Wizard is already authenticated."
|
|
585
|
-
: "Wizard authentication is missing; starting login.",
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
catch (error) {
|
|
589
|
-
return {
|
|
590
|
-
status: "unknown",
|
|
591
|
-
message: `Could not check Wizard auth status; starting login: ${toSafeErrorMessage(error)}`,
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
function wizardLoginResult(status, message, run) {
|
|
596
|
-
return {
|
|
597
|
-
status,
|
|
598
|
-
packageName: WIZARD_PACKAGE_NAME,
|
|
599
|
-
message,
|
|
600
|
-
loginUrl: run?.loginUrl,
|
|
601
|
-
startedAt: run?.startedAt,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
function extractLoginUrl(text) {
|
|
605
|
-
const matches = text.match(/https?:\/\/[^\s"'<>]+/g) ?? [];
|
|
606
|
-
for (const raw of matches) {
|
|
607
|
-
const candidate = raw.replace(/[),.;]+$/, "");
|
|
608
|
-
if (isSafeLoginUrl(candidate))
|
|
609
|
-
return candidate;
|
|
610
|
-
}
|
|
611
|
-
return undefined;
|
|
612
|
-
}
|
|
613
|
-
function isSafeLoginUrl(value) {
|
|
614
|
-
try {
|
|
615
|
-
const url = new URL(value);
|
|
616
|
-
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
617
|
-
return false;
|
|
618
|
-
const forbiddenQueryKeys = new Set([
|
|
619
|
-
"access_token",
|
|
620
|
-
"id_token",
|
|
621
|
-
"refresh_token",
|
|
622
|
-
"token",
|
|
623
|
-
"jwt",
|
|
624
|
-
"code",
|
|
625
|
-
]);
|
|
626
|
-
for (const key of url.searchParams.keys()) {
|
|
627
|
-
if (forbiddenQueryKeys.has(key.toLowerCase()))
|
|
628
|
-
return false;
|
|
629
|
-
}
|
|
630
|
-
if (/(^|[#&])(access_token|id_token|refresh_token|token|jwt|code)=/i.test(url.hash)) {
|
|
631
|
-
return false;
|
|
632
|
-
}
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
|
-
catch {
|
|
636
|
-
return false;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
export async function startWizardLogin(directory = homedir()) {
|
|
640
|
-
const existing = getActiveWizardLoginRun();
|
|
641
|
-
if (existing) {
|
|
642
|
-
return wizardLoginResult("already_running", existing.loginUrl
|
|
643
|
-
? "Wizard login is already running. Complete the browser flow."
|
|
644
|
-
: "Wizard login is already running. Complete the browser flow if it has opened.", existing);
|
|
645
|
-
}
|
|
646
|
-
if (activeWizardLoginStartPromise)
|
|
647
|
-
return await activeWizardLoginStartPromise;
|
|
648
|
-
const startPromise = startWizardLoginInternal(directory);
|
|
649
|
-
activeWizardLoginStartPromise = startPromise;
|
|
650
|
-
try {
|
|
651
|
-
return await startPromise;
|
|
652
|
-
}
|
|
653
|
-
finally {
|
|
654
|
-
if (activeWizardLoginStartPromise === startPromise) {
|
|
655
|
-
activeWizardLoginStartPromise = null;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
async function startWizardLoginInternal(directory) {
|
|
660
|
-
const authStatus = await checkWizardAuthStatus(directory);
|
|
661
|
-
if (authStatus.status === "configured") {
|
|
662
|
-
return {
|
|
663
|
-
status: "configured",
|
|
664
|
-
packageName: WIZARD_PACKAGE_NAME,
|
|
665
|
-
message: "Wizard is already authenticated; no login needed.",
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
if (authStatus.status === "package_unavailable") {
|
|
669
|
-
return {
|
|
670
|
-
status: "package_unavailable",
|
|
671
|
-
packageName: WIZARD_PACKAGE_NAME,
|
|
672
|
-
message: authStatus.message,
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
const existing = getActiveWizardLoginRun();
|
|
676
|
-
if (existing) {
|
|
677
|
-
return wizardLoginResult("already_running", existing.loginUrl
|
|
678
|
-
? "Wizard login is already running. Complete the browser flow."
|
|
679
|
-
: "Wizard login is already running. Complete the browser flow if it has opened.", existing);
|
|
680
|
-
}
|
|
681
|
-
const bin = resolveWizardBinCommand();
|
|
682
|
-
if (!bin.ok) {
|
|
683
|
-
return {
|
|
684
|
-
status: "package_unavailable",
|
|
685
|
-
packageName: WIZARD_PACKAGE_NAME,
|
|
686
|
-
message: bin.message,
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
const child = spawn(bin.bin.command, [...bin.bin.argsPrefix, "login"], {
|
|
690
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
691
|
-
env: process.env,
|
|
692
|
-
cwd: directory,
|
|
693
|
-
});
|
|
694
|
-
const run = { child, startedAt: Date.now() };
|
|
695
|
-
activeWizardLoginRun = run;
|
|
696
|
-
return await new Promise((resolve) => {
|
|
697
|
-
let settled = false;
|
|
698
|
-
let timeout;
|
|
699
|
-
const finish = (result) => {
|
|
700
|
-
if (settled)
|
|
701
|
-
return;
|
|
702
|
-
settled = true;
|
|
703
|
-
clearTimeout(timeout);
|
|
704
|
-
resolve(result);
|
|
705
|
-
};
|
|
706
|
-
const onData = (chunk) => {
|
|
707
|
-
if (run.loginUrl)
|
|
708
|
-
return;
|
|
709
|
-
const loginUrl = extractLoginUrl(chunk.toString("utf8"));
|
|
710
|
-
if (!loginUrl)
|
|
711
|
-
return;
|
|
712
|
-
run.loginUrl = loginUrl;
|
|
713
|
-
finish(wizardLoginResult("login_url_observed", "Wizard login started. Complete the browser flow to finish authentication.", run));
|
|
714
|
-
};
|
|
715
|
-
const onError = (error) => {
|
|
716
|
-
if (activeWizardLoginRun === run)
|
|
717
|
-
activeWizardLoginRun = null;
|
|
718
|
-
child.stdout?.off("data", onData);
|
|
719
|
-
child.stderr?.off("data", onData);
|
|
720
|
-
finish({
|
|
721
|
-
status: "spawn_failed",
|
|
722
|
-
packageName: WIZARD_PACKAGE_NAME,
|
|
723
|
-
message: `Could not start Wizard login: ${toSafeErrorMessage(error)}`,
|
|
724
|
-
});
|
|
725
|
-
};
|
|
726
|
-
const onExit = (code, signal) => {
|
|
727
|
-
if (activeWizardLoginRun === run)
|
|
728
|
-
activeWizardLoginRun = null;
|
|
729
|
-
child.stdout?.off("data", onData);
|
|
730
|
-
child.stderr?.off("data", onData);
|
|
731
|
-
const exitLabel = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
|
|
732
|
-
const completed = code === 0 && signal === null;
|
|
733
|
-
finish(wizardLoginResult(completed ? "completed" : "exited", completed
|
|
734
|
-
? "Wizard login completed."
|
|
735
|
-
: `Wizard login exited before emitting a login URL (${exitLabel}).`, run));
|
|
736
|
-
};
|
|
737
|
-
timeout = setTimeout(() => {
|
|
738
|
-
finish(wizardLoginResult("started", "Wizard login started. Complete the browser flow if it has opened.", run));
|
|
739
|
-
}, WIZARD_LOGIN_STARTUP_TIMEOUT_MS);
|
|
740
|
-
child.stdout?.on("data", onData);
|
|
741
|
-
child.stderr?.on("data", onData);
|
|
742
|
-
child.once("error", onError);
|
|
743
|
-
child.once("exit", onExit);
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* Dispatch a `rest_request` frame. Always resolves with a
|
|
748
|
-
* `RestResponseFrame` — handler exceptions are caught and translated.
|
|
749
|
-
*
|
|
750
|
-
* Status mapping:
|
|
751
|
-
* - 200: handler returned successfully (body is the handler's return value
|
|
752
|
-
* or `{ ok: true }` for void returns)
|
|
753
|
-
* - 400: `BadRequestError` thrown by handler, OR malformed body for a
|
|
754
|
-
* route that requires one, OR unknown route
|
|
755
|
-
* - 404: `NotFoundError` thrown by handler
|
|
756
|
-
* - 405: route matches but method doesn't (returned as 404 since we don't
|
|
757
|
-
* distinguish — the matcher treats it as "no route", consistent
|
|
758
|
-
* with the original Hono router which also 404'd)
|
|
759
|
-
* - 500: anything else; `error` field carries a sanitized message
|
|
760
|
-
*/
|
|
761
|
-
export async function handleRestRequest(frame, deps) {
|
|
762
|
-
const { reqId, method, path, body } = frame;
|
|
763
|
-
const logger = deps.logger ?? console;
|
|
764
|
-
const startedAt = performance.now();
|
|
765
|
-
logger.info?.(`[rest] start machineId=${frame.machineId} method=${method} path=${path} reqId=${reqId}`);
|
|
766
|
-
const match = matchRoute(method, path);
|
|
767
|
-
if (!match) {
|
|
768
|
-
logger.info?.(`[rest] finish machineId=${frame.machineId} method=${method} path=${path} reqId=${reqId} status=404 durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
769
|
-
return {
|
|
770
|
-
kind: "rest_response",
|
|
771
|
-
reqId,
|
|
772
|
-
status: 404,
|
|
773
|
-
body: { error: `Unknown route: ${method} ${path}` },
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
try {
|
|
777
|
-
const result = await dispatchRoute(match, body, deps);
|
|
778
|
-
logger.info?.(`[rest] finish machineId=${frame.machineId} method=${method} path=${path} reqId=${reqId} status=200 durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
779
|
-
return {
|
|
780
|
-
kind: "rest_response",
|
|
781
|
-
reqId,
|
|
782
|
-
status: 200,
|
|
783
|
-
body: result,
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
catch (err) {
|
|
787
|
-
if (err instanceof BadRequestError) {
|
|
788
|
-
logger.info?.(`[rest] finish machineId=${frame.machineId} method=${method} path=${path} reqId=${reqId} status=400 durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
789
|
-
return {
|
|
790
|
-
kind: "rest_response",
|
|
791
|
-
reqId,
|
|
792
|
-
status: 400,
|
|
793
|
-
body: { error: err.message, code: err.code },
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
if (err instanceof NotFoundError) {
|
|
797
|
-
logger.info?.(`[rest] finish machineId=${frame.machineId} method=${method} path=${path} reqId=${reqId} status=404 durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
798
|
-
return {
|
|
799
|
-
kind: "rest_response",
|
|
800
|
-
reqId,
|
|
801
|
-
status: 404,
|
|
802
|
-
body: { error: err.message, code: err.code },
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
// Unexpected — log locally so operators can correlate, but only
|
|
806
|
-
// return a sanitized message to the caller.
|
|
807
|
-
logger.error?.(`[dispatcher] unexpected error in ${method} ${path}:`, err);
|
|
808
|
-
logger.info?.(`[rest] finish machineId=${frame.machineId} method=${method} path=${path} reqId=${reqId} status=500 durationMs=${Math.round(performance.now() - startedAt)}`);
|
|
809
|
-
return {
|
|
810
|
-
kind: "rest_response",
|
|
811
|
-
reqId,
|
|
812
|
-
status: 500,
|
|
813
|
-
body: { error: "Internal error" },
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Inner switch: route → handler call. Separated so `handleRestRequest`
|
|
819
|
-
* owns ONE try/catch boundary for all error mapping.
|
|
820
|
-
*
|
|
821
|
-
* Meta publish is fired AFTER the handler returns successfully (i.e.
|
|
822
|
-
* after SQLite has committed) so subscribers never see a "create" event
|
|
823
|
-
* for a row the next read won't find. Publish failures are swallowed
|
|
824
|
-
* inside `safePublish` — the wire response goes back regardless.
|
|
825
|
-
*
|
|
826
|
-
* The body is `unknown` from the wire; each handler validates its own
|
|
827
|
-
* shape via the `BadRequestError`-throwing checks they already do.
|
|
828
|
-
*/
|
|
829
|
-
async function dispatchRoute(match, body, deps) {
|
|
830
|
-
const { store, manager, publishMetaEvent, logger } = deps;
|
|
831
|
-
const id = match.id ?? "";
|
|
832
|
-
switch (match.route) {
|
|
833
|
-
case "list_projects": {
|
|
834
|
-
const busyIds = manager.getBusySessionIds();
|
|
835
|
-
const projects = await handleListProjectsAsync(store);
|
|
836
|
-
// Batch all per-project session lookups into a SINGLE query (LEFT JOIN +
|
|
837
|
-
// GROUP BY) instead of calling listSessionsByProject(p.id) in a loop,
|
|
838
|
-
// which issued O(projects) queries each with a per-session correlated
|
|
839
|
-
// COUNT(*) subquery and blocked the event loop past the 30s relay
|
|
840
|
-
// deadline on large projects.
|
|
841
|
-
const sessionsByProject = await store.listSessionsByProjectIdsAsync(projects.map((p) => p.id));
|
|
842
|
-
return projects.map((p) => {
|
|
843
|
-
const sessions = sessionsByProject.get(p.id) ?? [];
|
|
844
|
-
const hasActiveSession = sessions.some((s) => busyIds.has(s.id));
|
|
845
|
-
return { ...p, hasActiveSession, busy: hasActiveSession };
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
case "create_project": {
|
|
849
|
-
const project = handleCreateProject(store, asObject(body));
|
|
850
|
-
safePublish(publishMetaEvent, logger, {
|
|
851
|
-
type: "project_created",
|
|
852
|
-
projectId: project.id,
|
|
853
|
-
});
|
|
854
|
-
return project;
|
|
855
|
-
}
|
|
856
|
-
case "update_project": {
|
|
857
|
-
const project = handleUpdateProject(store, id, asObject(body));
|
|
858
|
-
safePublish(publishMetaEvent, logger, {
|
|
859
|
-
type: "project_renamed",
|
|
860
|
-
projectId: project.id,
|
|
861
|
-
});
|
|
862
|
-
return project;
|
|
863
|
-
}
|
|
864
|
-
case "delete_project": {
|
|
865
|
-
// Order matters: tear down live streams BEFORE the row is gone so
|
|
866
|
-
// the bridges don't try to write to a deleted FK target. Mirrors
|
|
867
|
-
// the ordering the deleted Hono route used.
|
|
868
|
-
const project = await store.getProjectAsync(id);
|
|
869
|
-
if (project) {
|
|
870
|
-
const sessions = await store.listSessionsByProjectAsync(id);
|
|
871
|
-
manager.disposeProjectStreams(sessions.map((s) => s.id));
|
|
872
|
-
}
|
|
873
|
-
const result = handleDeleteProject(store, id);
|
|
874
|
-
// Publish AFTER the cascade has committed. We use `id` (the URL
|
|
875
|
-
// param) as the projectId — `handleDeleteProject` would have
|
|
876
|
-
// thrown 404 above if it didn't exist.
|
|
877
|
-
safePublish(publishMetaEvent, logger, {
|
|
878
|
-
type: "project_deleted",
|
|
879
|
-
projectId: id,
|
|
880
|
-
});
|
|
881
|
-
return result;
|
|
882
|
-
}
|
|
883
|
-
case "bind_studio": {
|
|
884
|
-
const result = await handleBindStudioProject(store, id, asObject(body));
|
|
885
|
-
safePublish(publishMetaEvent, logger, {
|
|
886
|
-
type: "project_studio_bound",
|
|
887
|
-
projectId: id,
|
|
888
|
-
studioProjectId: result.studioProjectId,
|
|
889
|
-
studioProjectName: result.studioProjectName,
|
|
890
|
-
});
|
|
891
|
-
// Refresh the X-Project-Id header on active bridges so agent-session
|
|
892
|
-
// credits are attributed to the newly bound project immediately.
|
|
893
|
-
manager.reloadProjectBindings().catch((err) => {
|
|
894
|
-
logger?.error?.(`[dispatcher] reloadProjectBindings failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
895
|
-
});
|
|
896
|
-
// Reload MCP config so the aexol-mcp extension picks up the new binding.
|
|
897
|
-
if (deps.mcpState) {
|
|
898
|
-
handleReloadMcpConfig(deps.mcpState).catch((err) => {
|
|
899
|
-
logger?.error?.(`[dispatcher] MCP reload after bind failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
return result;
|
|
903
|
-
}
|
|
904
|
-
case "unbind_studio": {
|
|
905
|
-
const result = await handleUnbindStudioProject(store, id);
|
|
906
|
-
if (result.studioProjectId) {
|
|
907
|
-
safePublish(publishMetaEvent, logger, {
|
|
908
|
-
type: "project_studio_unbound",
|
|
909
|
-
projectId: id,
|
|
910
|
-
studioProjectId: result.studioProjectId,
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
// Clear the X-Project-Id header on active bridges.
|
|
914
|
-
manager.reloadProjectBindings().catch((err) => {
|
|
915
|
-
logger?.error?.(`[dispatcher] reloadProjectBindings failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
916
|
-
});
|
|
917
|
-
// Reload MCP config so the aexol-mcp extension clears the old binding.
|
|
918
|
-
if (deps.mcpState) {
|
|
919
|
-
handleReloadMcpConfig(deps.mcpState).catch((err) => {
|
|
920
|
-
logger?.error?.(`[dispatcher] MCP reload after unbind failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
return result;
|
|
924
|
-
}
|
|
925
|
-
case "get_project_observations": {
|
|
926
|
-
const limitVal = Number(match.query?.get("limit") ?? 30);
|
|
927
|
-
return handleGetProjectObservations(store, id, Math.max(1, Math.min(100, limitVal || 30)));
|
|
928
|
-
}
|
|
929
|
-
case "list_project_sessions": {
|
|
930
|
-
const busyIds = manager.getBusySessionIds();
|
|
931
|
-
const sessions = await handleListSessionsByProjectAsync(store, id);
|
|
932
|
-
return sessions.map((s) => ({
|
|
933
|
-
...s,
|
|
934
|
-
hasActiveTurn: busyIds.has(s.id),
|
|
935
|
-
busy: busyIds.has(s.id),
|
|
936
|
-
}));
|
|
937
|
-
}
|
|
938
|
-
case "cleanup_project_sessions": {
|
|
939
|
-
const result = handleCleanupProjectSessions(store, id, asObject(body));
|
|
940
|
-
// Tear down any in-flight streams for sessions that are being deleted.
|
|
941
|
-
for (const sessionId of result.ids) {
|
|
942
|
-
manager.disposeSessionStream(sessionId);
|
|
943
|
-
}
|
|
944
|
-
for (const sessionId of result.ids) {
|
|
945
|
-
safePublish(publishMetaEvent, logger, {
|
|
946
|
-
type: "session_deleted",
|
|
947
|
-
projectId: id,
|
|
948
|
-
sessionId,
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
return { deleted: result.deleted };
|
|
952
|
-
}
|
|
953
|
-
case "cleanup_all_sessions": {
|
|
954
|
-
const result = handleCleanupAllSessions(store, asObject(body));
|
|
955
|
-
// Tear down any in-flight streams for sessions that are being deleted,
|
|
956
|
-
// across every project.
|
|
957
|
-
for (const sessionId of result.ids) {
|
|
958
|
-
manager.disposeSessionStream(sessionId);
|
|
959
|
-
}
|
|
960
|
-
for (const [sessionId, projectId] of Object.entries(result.projectIds)) {
|
|
961
|
-
safePublish(publishMetaEvent, logger, {
|
|
962
|
-
type: "session_deleted",
|
|
963
|
-
projectId,
|
|
964
|
-
sessionId,
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
return { deleted: result.deleted };
|
|
968
|
-
}
|
|
969
|
-
case "create_session": {
|
|
970
|
-
const session = handleCreateSession(store, asObject(body));
|
|
971
|
-
safePublish(publishMetaEvent, logger, {
|
|
972
|
-
type: "session_created",
|
|
973
|
-
projectId: session.projectId,
|
|
974
|
-
sessionId: session.id,
|
|
975
|
-
});
|
|
976
|
-
return session;
|
|
977
|
-
}
|
|
978
|
-
case "get_session": {
|
|
979
|
-
const detail = await handleGetSessionDetailAsync(store, id);
|
|
980
|
-
return {
|
|
981
|
-
...detail,
|
|
982
|
-
busy: manager.isBusy(id),
|
|
983
|
-
runState: manager.getRunState(id),
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
case "get_session_active_agent":
|
|
987
|
-
return await handleGetSessionActiveAgentAsync(store, id, deps.cwd ?? homedir(), deps.agentDir);
|
|
988
|
-
case "update_session_active_agent":
|
|
989
|
-
return await handleUpdateSessionActiveAgentAsync(store, id, asObject(body), manager, deps.cwd ?? homedir(), deps.agentDir);
|
|
990
|
-
case "get_session_messages":
|
|
991
|
-
return await handleGetSessionMessagesAsync(store, id, {
|
|
992
|
-
beforeSeq: match.query?.get("beforeSeq") ?? undefined,
|
|
993
|
-
beforeCreatedAt: match.query?.get("beforeCreatedAt") ?? undefined,
|
|
994
|
-
beforeId: match.query?.get("beforeId") ?? undefined,
|
|
995
|
-
limit: match.query?.get("limit") ?? undefined,
|
|
996
|
-
});
|
|
997
|
-
case "get_session_memory":
|
|
998
|
-
return handleGetSessionMemoryStatus(store, manager, id);
|
|
999
|
-
case "get_session_memory_details":
|
|
1000
|
-
return handleGetSessionMemoryDetails(store, manager, id);
|
|
1001
|
-
case "update_session": {
|
|
1002
|
-
const session = handleUpdateSession(store, id, asObject(body));
|
|
1003
|
-
safePublish(publishMetaEvent, logger, {
|
|
1004
|
-
type: "session_renamed",
|
|
1005
|
-
projectId: session.projectId,
|
|
1006
|
-
sessionId: session.id,
|
|
1007
|
-
});
|
|
1008
|
-
return session;
|
|
1009
|
-
}
|
|
1010
|
-
case "delete_session": {
|
|
1011
|
-
// Capture projectId BEFORE the row is gone — otherwise the
|
|
1012
|
-
// post-delete publish has no project to attribute to. Treat a
|
|
1013
|
-
// missing session here the same way `handleDeleteSession` does
|
|
1014
|
-
// (throws NotFoundError → 404), but capture first.
|
|
1015
|
-
const detail = store.getSession(id);
|
|
1016
|
-
// Same ordering rationale as delete_project.
|
|
1017
|
-
manager.disposeSessionStream(id);
|
|
1018
|
-
handleDeleteSession(store, id);
|
|
1019
|
-
if (detail) {
|
|
1020
|
-
safePublish(publishMetaEvent, logger, {
|
|
1021
|
-
type: "session_deleted",
|
|
1022
|
-
projectId: detail.projectId,
|
|
1023
|
-
sessionId: id,
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
return { ok: true };
|
|
1027
|
-
}
|
|
1028
|
-
case "compact_session":
|
|
1029
|
-
return await handleCompactSession(store, manager, id, body);
|
|
1030
|
-
case "remember_and_delete_session": {
|
|
1031
|
-
// Compact first — the compaction hook's persistProjectObservations
|
|
1032
|
-
// runs inside compactSession, writing reflections to cross-session
|
|
1033
|
-
// durable memory. On failure (e.g. no API key), we let the error
|
|
1034
|
-
// propagate so the session is NOT deleted.
|
|
1035
|
-
await handleRememberAndDeleteSession(store, manager, id);
|
|
1036
|
-
const detail = store.getSession(id);
|
|
1037
|
-
manager.disposeSessionStream(id);
|
|
1038
|
-
handleDeleteSession(store, id);
|
|
1039
|
-
if (detail) {
|
|
1040
|
-
safePublish(publishMetaEvent, logger, {
|
|
1041
|
-
type: "session_deleted",
|
|
1042
|
-
projectId: detail.projectId,
|
|
1043
|
-
sessionId: id,
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
return { ok: true };
|
|
1047
|
-
}
|
|
1048
|
-
case "fork_session": {
|
|
1049
|
-
const session = handleForkSession(store, id, asObject(body));
|
|
1050
|
-
safePublish(publishMetaEvent, logger, {
|
|
1051
|
-
type: "session_created",
|
|
1052
|
-
projectId: session.projectId,
|
|
1053
|
-
sessionId: session.id,
|
|
1054
|
-
});
|
|
1055
|
-
return session;
|
|
1056
|
-
}
|
|
1057
|
-
case "list_path_autocomplete": {
|
|
1058
|
-
const prefix = match.query?.get("prefix") ?? "";
|
|
1059
|
-
return await handlePathAutocomplete(prefix);
|
|
1060
|
-
}
|
|
1061
|
-
case "list_directory":
|
|
1062
|
-
return await handleListDirectory(asObject(body));
|
|
1063
|
-
case "get_home_directory":
|
|
1064
|
-
return handleGetHomeDirectory();
|
|
1065
|
-
case "search_project_files": {
|
|
1066
|
-
const searchQuery = match.query?.get("query") ?? "";
|
|
1067
|
-
return handleSearchProjectFiles(store, id, searchQuery);
|
|
1068
|
-
}
|
|
1069
|
-
case "enqueue_prompt":
|
|
1070
|
-
return handleEnqueuePrompt(store, manager, id, asObject(body));
|
|
1071
|
-
case "get_prompt_queue":
|
|
1072
|
-
return handleGetPromptQueue(store, id);
|
|
1073
|
-
case "remove_prompt":
|
|
1074
|
-
handleRemovePrompt(store, manager, id, match.itemId ?? "");
|
|
1075
|
-
return { ok: true };
|
|
1076
|
-
case "clear_prompt_queue":
|
|
1077
|
-
handleClearPromptQueue(store, manager, id);
|
|
1078
|
-
return { ok: true };
|
|
1079
|
-
case "list_mcp_status": {
|
|
1080
|
-
if (!deps.mcpState) {
|
|
1081
|
-
return [];
|
|
1082
|
-
}
|
|
1083
|
-
return handleListMcpStatus(deps.mcpState);
|
|
1084
|
-
}
|
|
1085
|
-
case "reload_mcp_config": {
|
|
1086
|
-
return await handleReloadMcpConfig(deps.mcpState);
|
|
1087
|
-
}
|
|
1088
|
-
case "refresh_native_extensions": {
|
|
1089
|
-
if (!deps.refreshNativeExtensions) {
|
|
1090
|
-
return { ok: true };
|
|
1091
|
-
}
|
|
1092
|
-
await deps.refreshNativeExtensions();
|
|
1093
|
-
return { ok: true };
|
|
1094
|
-
}
|
|
1095
|
-
case "refresh_cloud_agents": {
|
|
1096
|
-
const teamId = asObject(body).teamId;
|
|
1097
|
-
if (typeof teamId !== "string" || teamId.length === 0) {
|
|
1098
|
-
throw new BadRequestError("teamId is required");
|
|
1099
|
-
}
|
|
1100
|
-
if (deps.refreshCloudAgents)
|
|
1101
|
-
await deps.refreshCloudAgents(teamId);
|
|
1102
|
-
return { ok: true };
|
|
1103
|
-
}
|
|
1104
|
-
case "wizard_login": {
|
|
1105
|
-
return await (deps.startWizardLogin ?? (() => startWizardLogin(deps.cwd ?? homedir())))();
|
|
1106
|
-
}
|
|
1107
|
-
case "get_settings":
|
|
1108
|
-
return await handleGetSettingsAsync(deps.cwd ?? homedir());
|
|
1109
|
-
case "put_settings":
|
|
1110
|
-
return await handlePutSettingsAsync(deps.cwd ?? homedir(), asObject(body));
|
|
1111
|
-
case "get_agent_settings":
|
|
1112
|
-
return await handleGetAgentSettingsAsync(deps.cwd ?? homedir(), deps.agentDir);
|
|
1113
|
-
case "put_agent_settings":
|
|
1114
|
-
return await handlePutAgentSettingsAsync(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
|
|
1115
|
-
case "get_prompt_mutation_settings":
|
|
1116
|
-
return handleGetPromptMutationSettings(deps.cwd ?? homedir(), deps.agentDir);
|
|
1117
|
-
case "put_prompt_mutation_settings":
|
|
1118
|
-
return handlePutPromptMutationSettings(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
|
|
1119
|
-
case "list_agents": {
|
|
1120
|
-
const agentCwd = match.query?.get("cwd") || deps.cwd || homedir();
|
|
1121
|
-
const teamId = match.query?.get("teamId") || undefined;
|
|
1122
|
-
// Kick off cloud refresh in the background without blocking the response.
|
|
1123
|
-
// The previous version awaited refreshCloudAgents (network → backend GraphQL)
|
|
1124
|
-
// before returning, so every page refresh stalled the entire REST queue
|
|
1125
|
-
// behind this one slow call while 5-7 other rest_requests timed out.
|
|
1126
|
-
// Background refresh self-dedupes via CloudAgentCacheEntry.inFlight, so
|
|
1127
|
-
// bursts coalesce; stale cache renders immediately and the picker catches
|
|
1128
|
-
// up on the next meta_event/live refresh.
|
|
1129
|
-
if (teamId && deps.refreshCloudAgents) {
|
|
1130
|
-
void deps.refreshCloudAgents(teamId).catch((err) => {
|
|
1131
|
-
logger?.error?.(`[dispatcher] cloud agent refresh failed:`, err);
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
const agents = handleListAgents(agentCwd, deps.agentDir, teamId);
|
|
1135
|
-
return agents;
|
|
1136
|
-
}
|
|
1137
|
-
case "list_dev_processes":
|
|
1138
|
-
return handleListDevProcesses(deps.devProcessRegistry);
|
|
1139
|
-
case "start_dev_process":
|
|
1140
|
-
return handleStartDevProcess(deps.devProcessRegistry, store, asObject(body), deps.cwd);
|
|
1141
|
-
case "kill_dev_process":
|
|
1142
|
-
return handleKillDevProcess(deps.devProcessRegistry, id);
|
|
1143
|
-
case "restart_dev_process":
|
|
1144
|
-
return handleRestartDevProcess(deps.devProcessRegistry, id);
|
|
1145
|
-
case "scan_dev_servers":
|
|
1146
|
-
return handleScanDevServers(store, deps.devProcessRegistry);
|
|
1147
|
-
case "kill_dev_server":
|
|
1148
|
-
return await handleKillDetectedDevServer(store, Number(id), match.port);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
/**
|
|
1152
|
-
* Invoke `publishMetaEvent` if provided, swallowing any throw. The
|
|
1153
|
-
* caller has already returned a 200 to the browser; a publish failure
|
|
1154
|
-
* (e.g. relay socket just dropped) must NEVER turn a successful
|
|
1155
|
-
* mutation into a wire-level error.
|
|
1156
|
-
*/
|
|
1157
|
-
function safePublish(publish, logger, event) {
|
|
1158
|
-
if (!publish)
|
|
1159
|
-
return;
|
|
1160
|
-
try {
|
|
1161
|
-
publish(event);
|
|
1162
|
-
}
|
|
1163
|
-
catch (err) {
|
|
1164
|
-
(logger ?? console).error?.(`[dispatcher] publishMetaEvent failed for ${event.type}:`, err);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Coerce an `unknown` body to a plain object for the handler. Returns an
|
|
1169
|
-
* empty object when the body is missing/non-object so the handler's own
|
|
1170
|
-
* field checks produce the canonical error message ("name (string) is
|
|
1171
|
-
* required") instead of a confusing "body must be object" from us.
|
|
1172
|
-
*/
|
|
1173
|
-
function asObject(body) {
|
|
1174
|
-
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
1175
|
-
return body;
|
|
1176
|
-
}
|
|
1177
|
-
return {};
|
|
1178
|
-
}
|
|
54
|
+
// Extracted modules — re-exported for backwards compatibility with the
|
|
55
|
+
// original single-file dispatcher.
|
|
56
|
+
export { matchRoute } from "./routing.js";
|
|
57
|
+
export { handleRestRequest } from "./rest.js";
|
|
58
|
+
export { startWizardLogin } from "./wizard-login.js";
|
|
1179
59
|
// ---------------------------------------------------------------------------
|
|
1180
60
|
// Helpers
|
|
1181
61
|
// ---------------------------------------------------------------------------
|
|
@@ -1560,6 +440,17 @@ export async function handleSubscribe(frame, deps) {
|
|
|
1560
440
|
export function handleCancelTurn(frame, deps) {
|
|
1561
441
|
deps.manager.cancelTurn(frame.sessionId);
|
|
1562
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Dispatch a `stop_tool_call` frame. Stops ONE hung/long-running tool call
|
|
445
|
+
* for the session — that call is aborted, the agent receives a synthesized
|
|
446
|
+
* user-stop tool result, and the turn continues (unlike `cancel_turn`).
|
|
447
|
+
*
|
|
448
|
+
* Guarded: unknown toolCallId → no crash, the manager broadcasts a
|
|
449
|
+
* best-effort `tool_stop_result` info frame.
|
|
450
|
+
*/
|
|
451
|
+
export async function handleStopToolCall(frame, deps) {
|
|
452
|
+
await deps.manager.stopToolCall(frame.sessionId, frame.toolCallId, frame.reason);
|
|
453
|
+
}
|
|
1563
454
|
/**
|
|
1564
455
|
* Detach every subscriber the dispatcher has attached. Called by
|
|
1565
456
|
* `serve.ts` on relay disconnect / shutdown so the underlying spectral
|