@wrongstack/webui-server 0.284.1 → 0.286.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +11 -2300
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1807 -539
- package/dist/index.js.map +7 -1
- package/dist/server/autophase-routes.d.ts +10 -0
- package/dist/server/autophase-routes.d.ts.map +1 -0
- package/dist/server/autophase-ws-handler.d.ts +87 -0
- package/dist/server/autophase-ws-handler.d.ts.map +1 -0
- package/dist/server/backend-services.d.ts +103 -0
- package/dist/server/backend-services.d.ts.map +1 -0
- package/dist/server/boot.d.ts +19 -0
- package/dist/server/boot.d.ts.map +1 -0
- package/dist/server/brain-routes.d.ts +11 -0
- package/dist/server/brain-routes.d.ts.map +1 -0
- package/dist/server/codebase-indexing.d.ts +15 -0
- package/dist/server/codebase-indexing.d.ts.map +1 -0
- package/dist/server/collaboration-ws-handler.d.ts +167 -0
- package/dist/server/collaboration-ws-handler.d.ts.map +1 -0
- package/dist/server/completion-handlers.d.ts +39 -0
- package/dist/server/completion-handlers.d.ts.map +1 -0
- package/dist/server/connection-handler.d.ts +72 -0
- package/dist/server/connection-handler.d.ts.map +1 -0
- package/dist/server/context-meta.d.ts +28 -0
- package/dist/server/context-meta.d.ts.map +1 -0
- package/dist/server/custom-context-modes.d.ts +43 -0
- package/dist/server/custom-context-modes.d.ts.map +1 -0
- package/dist/server/design-handlers.d.ts +40 -0
- package/dist/server/design-handlers.d.ts.map +1 -0
- package/dist/server/discover-mailbox-bridge.d.ts +26 -0
- package/dist/server/discover-mailbox-bridge.d.ts.map +1 -0
- package/dist/server/entry.d.ts +2 -2
- package/dist/server/entry.d.ts.map +1 -0
- package/dist/server/entry.js +1595 -562
- package/dist/server/entry.js.map +7 -1
- package/dist/server/eternal-iteration-broadcast.d.ts +11 -0
- package/dist/server/eternal-iteration-broadcast.d.ts.map +1 -0
- package/dist/server/file-handlers.d.ts +45 -0
- package/dist/server/file-handlers.d.ts.map +1 -0
- package/dist/server/file-picker.d.ts +26 -0
- package/dist/server/file-picker.d.ts.map +1 -0
- package/dist/server/git-handlers.d.ts +55 -0
- package/dist/server/git-handlers.d.ts.map +1 -0
- package/dist/server/goal-handlers.d.ts +19 -0
- package/dist/server/goal-handlers.d.ts.map +1 -0
- package/dist/server/handlers/index.d.ts +12 -0
- package/dist/server/handlers/index.d.ts.map +1 -0
- package/dist/server/handlers/worklist-handlers.d.ts +49 -0
- package/dist/server/handlers/worklist-handlers.d.ts.map +1 -0
- package/dist/server/handlers.d.ts +1 -51
- package/dist/server/handlers.js +17 -8
- package/dist/server/handlers.js.map +7 -1
- package/dist/server/http-server/analytics-handler.d.ts +35 -0
- package/dist/server/http-server/analytics-handler.d.ts.map +1 -0
- package/dist/server/http-server/api-handlers.d.ts +44 -0
- package/dist/server/http-server/api-handlers.d.ts.map +1 -0
- package/dist/server/http-server.d.ts +108 -0
- package/dist/server/http-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +93 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/instance-registry.d.ts +63 -0
- package/dist/server/instance-registry.d.ts.map +1 -0
- package/dist/server/kanban-routes.d.ts +7 -0
- package/dist/server/kanban-routes.d.ts.map +1 -0
- package/dist/server/lifecycle.d.ts +51 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/mailbox-handlers.d.ts +63 -0
- package/dist/server/mailbox-handlers.d.ts.map +1 -0
- package/dist/server/mailbox-routes.d.ts +11 -0
- package/dist/server/mailbox-routes.d.ts.map +1 -0
- package/dist/server/mcp-handlers.d.ts +55 -0
- package/dist/server/mcp-handlers.d.ts.map +1 -0
- package/dist/server/mcp-routes.d.ts +41 -0
- package/dist/server/mcp-routes.d.ts.map +1 -0
- package/dist/server/memory-handlers.d.ts +28 -0
- package/dist/server/memory-handlers.d.ts.map +1 -0
- package/dist/server/message-dispatcher.d.ts +41 -0
- package/dist/server/message-dispatcher.d.ts.map +1 -0
- package/dist/server/mode-handlers.d.ts +50 -0
- package/dist/server/mode-handlers.d.ts.map +1 -0
- package/dist/server/mode-routes.d.ts +8 -0
- package/dist/server/mode-routes.d.ts.map +1 -0
- package/dist/server/model-auto-discovery.d.ts +9 -0
- package/dist/server/model-auto-discovery.d.ts.map +1 -0
- package/dist/server/model-catalog.d.ts +16 -0
- package/dist/server/model-catalog.d.ts.map +1 -0
- package/dist/server/open-browser.d.ts +16 -0
- package/dist/server/open-browser.d.ts.map +1 -0
- package/dist/server/path-containment.d.ts +3 -0
- package/dist/server/path-containment.d.ts.map +1 -0
- package/dist/server/pending-confirms.d.ts +14 -0
- package/dist/server/pending-confirms.d.ts.map +1 -0
- package/dist/server/port-utils.d.ts +28 -0
- package/dist/server/port-utils.d.ts.map +1 -0
- package/dist/server/pre-context-services.d.ts +60 -0
- package/dist/server/pre-context-services.d.ts.map +1 -0
- package/dist/server/pref-helpers.d.ts +43 -0
- package/dist/server/pref-helpers.d.ts.map +1 -0
- package/dist/server/prefs-routes.d.ts +27 -0
- package/dist/server/prefs-routes.d.ts.map +1 -0
- package/dist/server/process-handlers.d.ts +20 -0
- package/dist/server/process-handlers.d.ts.map +1 -0
- package/dist/server/project-handlers.d.ts +53 -0
- package/dist/server/project-handlers.d.ts.map +1 -0
- package/dist/server/project-routes.d.ts +10 -0
- package/dist/server/project-routes.d.ts.map +1 -0
- package/dist/server/projects-manifest.d.ts +18 -0
- package/dist/server/projects-manifest.d.ts.map +1 -0
- package/dist/server/prompts-handlers.d.ts +38 -0
- package/dist/server/prompts-handlers.d.ts.map +1 -0
- package/dist/server/provider-config-io.d.ts +15 -0
- package/dist/server/provider-config-io.d.ts.map +1 -0
- package/dist/server/provider-config-standalone.d.ts +13 -0
- package/dist/server/provider-config-standalone.d.ts.map +1 -0
- package/dist/server/provider-handlers.d.ts +74 -0
- package/dist/server/provider-handlers.d.ts.map +1 -0
- package/dist/server/provider-keys.d.ts +53 -0
- package/dist/server/provider-keys.d.ts.map +1 -0
- package/dist/server/provider-routes.d.ts +13 -0
- package/dist/server/provider-routes.d.ts.map +1 -0
- package/dist/server/provider-store.d.ts +26 -0
- package/dist/server/provider-store.d.ts.map +1 -0
- package/dist/server/routes.d.ts +187 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/sdd-board-routes.d.ts +11 -0
- package/dist/server/sdd-board-routes.d.ts.map +1 -0
- package/dist/server/sdd-board-ws-handler.d.ts +57 -0
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -0
- package/dist/server/sdd-wizard-routes.d.ts +16 -0
- package/dist/server/sdd-wizard-routes.d.ts.map +1 -0
- package/dist/server/sdd-wizard-wiring.d.ts +26 -0
- package/dist/server/sdd-wizard-wiring.d.ts.map +1 -0
- package/dist/server/sdd-wizard-ws-handler.d.ts +66 -0
- package/dist/server/sdd-wizard-ws-handler.d.ts.map +1 -0
- package/dist/server/server-runtime.d.ts +118 -0
- package/dist/server/server-runtime.d.ts.map +1 -0
- package/dist/server/session-handlers.d.ts +57 -0
- package/dist/server/session-handlers.d.ts.map +1 -0
- package/dist/server/session-routes.d.ts +23 -0
- package/dist/server/session-routes.d.ts.map +1 -0
- package/dist/server/setup-events.d.ts +67 -0
- package/dist/server/setup-events.d.ts.map +1 -0
- package/dist/server/setup-screen.d.ts +44 -0
- package/dist/server/setup-screen.d.ts.map +1 -0
- package/dist/server/shell-git-routes.d.ts +10 -0
- package/dist/server/shell-git-routes.d.ts.map +1 -0
- package/dist/server/shell-open.d.ts +12 -0
- package/dist/server/shell-open.d.ts.map +1 -0
- package/dist/server/skills-handlers.d.ts +77 -0
- package/dist/server/skills-handlers.d.ts.map +1 -0
- package/dist/server/specs-routes.d.ts +11 -0
- package/dist/server/specs-routes.d.ts.map +1 -0
- package/dist/server/specs-ws-handler.d.ts +34 -0
- package/dist/server/specs-ws-handler.d.ts.map +1 -0
- package/dist/server/standalone-session-identity.d.ts +35 -0
- package/dist/server/standalone-session-identity.d.ts.map +1 -0
- package/dist/server/start-webui.d.ts +29 -0
- package/dist/server/start-webui.d.ts.map +1 -0
- package/dist/server/terminal-ws-handler.d.ts +70 -0
- package/dist/server/terminal-ws-handler.d.ts.map +1 -0
- package/dist/server/token-estimator.d.ts +66 -0
- package/dist/server/token-estimator.d.ts.map +1 -0
- package/dist/server/types.d.ts +143 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/usage-cost.d.ts +39 -0
- package/dist/server/usage-cost.d.ts.map +1 -0
- package/dist/server/worktree-ws-handler.d.ts +67 -0
- package/dist/server/worktree-ws-handler.d.ts.map +1 -0
- package/dist/server/ws-auth.d.ts +83 -0
- package/dist/server/ws-auth.d.ts.map +1 -0
- package/dist/server/ws-payload-validation.d.ts +126 -0
- package/dist/server/ws-payload-validation.d.ts.map +1 -0
- package/dist/server/ws-utils.d.ts +38 -0
- package/dist/server/ws-utils.d.ts.map +1 -0
- package/dist/server/zip.d.ts +9 -0
- package/dist/server/zip.d.ts.map +1 -0
- package/package.json +11 -12
package/dist/server/entry.js
CHANGED
|
@@ -1,35 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DefaultSecretScrubber, ToolValidationError, expectDefined, createDefaultPipelines, resolveSessionLoggingConfig, createSessionEventBridge, watchProviderConfig, atomicWrite, bootConfig as bootConfig$1, DefaultModelsRegistry, TOKENS, ProviderRegistry, ToolRegistry, DefaultMemoryStore, EventBus, makeMailboxTool, makeMailSendTool, makeMailInboxTool, makeFleetStatusTool, applyToolDescriptionModes, applyToolResultRenderModes, DefaultSessionStore, getSessionRegistry, DEFAULT_SESSION_PRUNE_DAYS, DefaultSessionReader, AnnotationsStore, FleetNotifier, AgentStatusTracker, DefaultModeStore, DefaultSkillLoader, SkillInstaller, DefaultPromptLoader, PromptUsageStore, DefaultSystemPromptBuilder, GlobalMailbox, Context, resolveContextWindowPolicy, CollaborationBus, collabInjectMiddleware, collabPauseMiddleware, installDesignStudioMiddleware, createStrategyCompactor, AutoCompactionMiddleware, estimateRequestTokensCalibrated, ToolExecutor, DEFAULT_TOOLS_CONFIG, Agent, ObservableBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, resolveProjectDir, wstackGlobalRoot, createAutonomyBrain, PhaseStore, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, AutoPhasePlanner, assignNickname, computeTaskProgress, DEFAULT_CONTEXT_WINDOW_MODE_ID, repairToolUseAdjacency, resolveWstackPaths, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, resolveProviderModelList, listContextWindowModes, projectSlug, allServers, ConfigError, deserializeTaskGraph, serializeTaskGraph, loadActiveKit, getDesignKitLoader, applyTokenOverrides, runDesignVerify, isDesignStack, materializeTokens, recordOverrides, setDesignOverrides, getDesignState, setActiveKit, recordKitChoice } from '@wrongstack/core';
|
|
3
|
-
import { spawnSync, spawn } from 'child_process';
|
|
4
|
-
import { toErrorMessage, atomicWrite as atomicWrite$1, resolveWstackPaths as resolveWstackPaths$1, wstackGlobalRoot as wstackGlobalRoot$1 } from '@wrongstack/core/utils';
|
|
5
|
-
import * as path5 from 'path';
|
|
6
|
-
import path5__default, { resolve, join, sep, dirname } from 'path';
|
|
7
|
-
import { searchCodebaseIndex } from '@wrongstack/tools/codebase-index/index';
|
|
8
|
-
import { randomUUID, randomBytes, timingSafeEqual } from 'crypto';
|
|
9
|
-
import { WebSocket, WebSocketServer } from 'ws';
|
|
10
|
-
import * as fs4 from 'fs/promises';
|
|
11
|
-
import * as http from 'http';
|
|
12
|
-
import { Buffer } from 'buffer';
|
|
13
|
-
import * as os from 'os';
|
|
14
|
-
import { MCPRegistry, discoverMcp, restartMcp, disableMcp, enableMcp, removeMcp, updateMcp, addMcp, listMcp } from '@wrongstack/mcp';
|
|
15
|
-
import * as net from 'net';
|
|
16
|
-
import { decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core/security';
|
|
17
|
-
import { SpecStore, TaskGraphStore, SddBoardStore, applySddLifecycle, SddRunRegistry, SddInterviewDriver, cleanupStaleSddWorktrees, makeCommandVerifier, SddSupervisor, makeLlmSubtaskGenerator, startSddRun } from '@wrongstack/sdd';
|
|
18
|
-
import * as fs12 from 'fs';
|
|
19
|
-
import { watch, existsSync, promises } from 'fs';
|
|
20
|
-
import JSZip from 'jszip';
|
|
21
|
-
import { createRequire } from 'module';
|
|
22
|
-
import { fileURLToPath } from 'url';
|
|
23
|
-
import { makeProviderFromConfig, buildProviderFactoriesFromRegistry, discoverOpenAICompatibleModels, capabilitiesFor, COMPATIBLE_PRESETS } from '@wrongstack/providers';
|
|
24
|
-
import { DefaultTokenCounter } from '@wrongstack/core/infrastructure';
|
|
25
|
-
import { createDefaultContainer, makeLightSubagentFactory } from '@wrongstack/runtime';
|
|
26
|
-
import { ensureSessionShell, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, configureExecPolicy, configureDangerBypass, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost } from '@wrongstack/tools';
|
|
27
|
-
import { readLiveLock } from '@wrongstack/core/coordination';
|
|
28
|
-
import { removeColumn, addColumn, getTask, removeTask, assignTask, updateGoalMetricOnTask, addGoalMetricToTask, releaseTaskClaim, claimReadyTask, getTaskChain, setTaskChain, copyTaskToBoard, transferTaskToBoard, moveTask, updateTask, mergeTasks, splitTask, addTask, syncBoardFromTaskGraph, exportBoardToTaskGraph, getKanbanOrchestrationSnapshot, listReadyTasks, createBoard, generateBoardFromDescription, parseLinesIntoTasks, getBoard, removeBoard, duplicateBoard, updateBoard, getKanbanQueueHealth, listBoards } from '@wrongstack/kanban';
|
|
29
|
-
import { beginOAuthLogin } from '@wrongstack/providers/oauth';
|
|
30
|
-
import { probeLocalLlm } from '@wrongstack/runtime/probe';
|
|
31
|
-
|
|
32
2
|
// src/server/entry.ts
|
|
3
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core";
|
|
4
|
+
|
|
5
|
+
// src/server/autophase-ws-handler.ts
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
8
|
+
import {
|
|
9
|
+
assignNickname,
|
|
10
|
+
AutoPhasePlanner,
|
|
11
|
+
PhaseGraphBuilder,
|
|
12
|
+
PhaseOrchestrator,
|
|
13
|
+
PhaseStore,
|
|
14
|
+
WorktreeManager
|
|
15
|
+
} from "@wrongstack/core";
|
|
33
16
|
function deriveTitle(goal) {
|
|
34
17
|
const firstLine = goal.split("\n").map((l) => l.trim()).find(Boolean);
|
|
35
18
|
if (!firstLine) return "AutoPhase";
|
|
@@ -520,6 +503,14 @@ Type: ${task.type}`;
|
|
|
520
503
|
}
|
|
521
504
|
}
|
|
522
505
|
};
|
|
506
|
+
|
|
507
|
+
// src/server/completion-handlers.ts
|
|
508
|
+
import * as path from "node:path";
|
|
509
|
+
import { searchCodebaseIndex } from "@wrongstack/tools/codebase-index/index";
|
|
510
|
+
|
|
511
|
+
// src/server/ws-utils.ts
|
|
512
|
+
import { randomBytes } from "node:crypto";
|
|
513
|
+
import { WebSocket } from "ws";
|
|
523
514
|
function send(ws, msg) {
|
|
524
515
|
if (ws.readyState === WebSocket.OPEN) {
|
|
525
516
|
ws.send(JSON.stringify(msg));
|
|
@@ -651,8 +642,8 @@ async function handleCompletionRequest(ws, msg, opts) {
|
|
|
651
642
|
return;
|
|
652
643
|
}
|
|
653
644
|
const payload = parsed.payload;
|
|
654
|
-
const projectRoot =
|
|
655
|
-
const resolved =
|
|
645
|
+
const projectRoot = path.resolve(opts.projectRoot);
|
|
646
|
+
const resolved = path.resolve(projectRoot, payload.filePath);
|
|
656
647
|
if (!isInside(projectRoot, resolved)) {
|
|
657
648
|
send(ws, {
|
|
658
649
|
type: "completion.result",
|
|
@@ -1051,7 +1042,7 @@ function buildSearchQuery(linePrefix, filePath) {
|
|
|
1051
1042
|
if (memberMatch?.[1]) return memberMatch[1];
|
|
1052
1043
|
const token = linePrefix.match(/([A-Za-z_$][\w$]*)$/)?.[1];
|
|
1053
1044
|
if (token && token.length >= 2) return token;
|
|
1054
|
-
return
|
|
1045
|
+
return path.basename(filePath, path.extname(filePath));
|
|
1055
1046
|
}
|
|
1056
1047
|
function currentLinePrefix(prefix) {
|
|
1057
1048
|
const idx = Math.max(prefix.lastIndexOf("\n"), prefix.lastIndexOf("\r"));
|
|
@@ -1081,11 +1072,16 @@ function head(value, max) {
|
|
|
1081
1072
|
return value.length <= max ? value : value.slice(0, max);
|
|
1082
1073
|
}
|
|
1083
1074
|
function isInside(root, target) {
|
|
1084
|
-
return target === root || target.startsWith(root +
|
|
1075
|
+
return target === root || target.startsWith(root + path.sep);
|
|
1085
1076
|
}
|
|
1077
|
+
|
|
1078
|
+
// src/server/custom-context-modes.ts
|
|
1079
|
+
import { listContextWindowModes, atomicWrite } from "@wrongstack/core";
|
|
1080
|
+
import * as fs from "node:fs/promises";
|
|
1081
|
+
import * as path2 from "node:path";
|
|
1086
1082
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
1087
1083
|
function storePath(wrongstackDir) {
|
|
1088
|
-
return
|
|
1084
|
+
return path2.join(wrongstackDir, STORE_FILENAME);
|
|
1089
1085
|
}
|
|
1090
1086
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
1091
1087
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -1093,7 +1089,7 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
1093
1089
|
const load2 = async () => {
|
|
1094
1090
|
modes.clear();
|
|
1095
1091
|
try {
|
|
1096
|
-
const raw = await
|
|
1092
|
+
const raw = await fs.readFile(storePath(wrongstackDir), "utf8");
|
|
1097
1093
|
const parsed = JSON.parse(raw);
|
|
1098
1094
|
if (Array.isArray(parsed.modes)) {
|
|
1099
1095
|
for (const m of parsed.modes) {
|
|
@@ -1198,6 +1194,23 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
1198
1194
|
};
|
|
1199
1195
|
return { modes, load: load2, save: save2, create, update, remove, list };
|
|
1200
1196
|
}
|
|
1197
|
+
|
|
1198
|
+
// src/server/design-handlers.ts
|
|
1199
|
+
import * as fs2 from "node:fs/promises";
|
|
1200
|
+
import * as path3 from "node:path";
|
|
1201
|
+
import {
|
|
1202
|
+
applyTokenOverrides,
|
|
1203
|
+
getDesignKitLoader,
|
|
1204
|
+
getDesignState,
|
|
1205
|
+
isDesignStack,
|
|
1206
|
+
loadActiveKit,
|
|
1207
|
+
materializeTokens,
|
|
1208
|
+
recordKitChoice,
|
|
1209
|
+
recordOverrides,
|
|
1210
|
+
runDesignVerify,
|
|
1211
|
+
setActiveKit,
|
|
1212
|
+
setDesignOverrides
|
|
1213
|
+
} from "@wrongstack/core";
|
|
1201
1214
|
function readOverrides(value) {
|
|
1202
1215
|
const out = {};
|
|
1203
1216
|
if (value && typeof value === "object") {
|
|
@@ -1345,9 +1358,9 @@ async function handleDesignMaterialize(ws, ctx, msg) {
|
|
|
1345
1358
|
kitId: active.kit,
|
|
1346
1359
|
outPath: typeof payload.out === "string" ? payload.out : void 0
|
|
1347
1360
|
});
|
|
1348
|
-
const abs =
|
|
1349
|
-
await
|
|
1350
|
-
await
|
|
1361
|
+
const abs = path3.join(ctx.projectRoot, result.path);
|
|
1362
|
+
await fs2.mkdir(path3.dirname(abs), { recursive: true });
|
|
1363
|
+
await fs2.writeFile(abs, result.content);
|
|
1351
1364
|
send(ws, {
|
|
1352
1365
|
type: "design.materialize",
|
|
1353
1366
|
payload: { ok: true, path: result.path, format: result.format, stack }
|
|
@@ -1406,6 +1419,11 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
1406
1419
|
};
|
|
1407
1420
|
}
|
|
1408
1421
|
|
|
1422
|
+
// src/server/file-handlers.ts
|
|
1423
|
+
import * as fs4 from "node:fs/promises";
|
|
1424
|
+
import * as path5 from "node:path";
|
|
1425
|
+
import { atomicWrite as atomicWrite2, ToolValidationError as ToolValidationError2 } from "@wrongstack/core";
|
|
1426
|
+
|
|
1409
1427
|
// src/server/file-picker.ts
|
|
1410
1428
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1411
1429
|
".git",
|
|
@@ -1453,15 +1471,20 @@ function rankFiles(paths, query, limit) {
|
|
|
1453
1471
|
scored.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
1454
1472
|
return scored.slice(0, limit).map((s) => s.path);
|
|
1455
1473
|
}
|
|
1474
|
+
|
|
1475
|
+
// src/server/path-containment.ts
|
|
1476
|
+
import * as fs3 from "node:fs/promises";
|
|
1477
|
+
import * as path4 from "node:path";
|
|
1478
|
+
import { ToolValidationError } from "@wrongstack/core";
|
|
1456
1479
|
function isPathInside(root, target) {
|
|
1457
|
-
const relative4 =
|
|
1458
|
-
return relative4 === "" || !relative4.startsWith("..") && !
|
|
1480
|
+
const relative4 = path4.relative(root, target);
|
|
1481
|
+
return relative4 === "" || !relative4.startsWith("..") && !path4.isAbsolute(relative4);
|
|
1459
1482
|
}
|
|
1460
1483
|
async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
1461
|
-
const resolved =
|
|
1484
|
+
const resolved = path4.resolve(projectRoot, inputPath);
|
|
1462
1485
|
let stat2;
|
|
1463
1486
|
try {
|
|
1464
|
-
stat2 = await
|
|
1487
|
+
stat2 = await fs3.stat(resolved);
|
|
1465
1488
|
} catch {
|
|
1466
1489
|
throw new ToolValidationError({
|
|
1467
1490
|
message: `Directory not found or not accessible: ${resolved}`,
|
|
@@ -1475,8 +1498,8 @@ async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
|
1475
1498
|
});
|
|
1476
1499
|
}
|
|
1477
1500
|
const [realProjectRoot, realResolved] = await Promise.all([
|
|
1478
|
-
|
|
1479
|
-
|
|
1501
|
+
fs3.realpath(projectRoot),
|
|
1502
|
+
fs3.realpath(resolved)
|
|
1480
1503
|
]);
|
|
1481
1504
|
if (!isPathInside(realProjectRoot, realResolved)) {
|
|
1482
1505
|
throw new ToolValidationError({
|
|
@@ -1491,14 +1514,14 @@ async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
|
1491
1514
|
async function resolveFileInsideProject(projectRoot, filePath) {
|
|
1492
1515
|
const resolved = path5.resolve(projectRoot, filePath);
|
|
1493
1516
|
if (!isPathInside(projectRoot, resolved)) {
|
|
1494
|
-
throw new
|
|
1517
|
+
throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
|
|
1495
1518
|
}
|
|
1496
1519
|
const { parent, base } = splitParentAndBase(resolved);
|
|
1497
1520
|
const realProjectRoot = await fs4.realpath(projectRoot);
|
|
1498
1521
|
const realParent = await realpathAllowMissing(parent);
|
|
1499
1522
|
const realFull = path5.join(realParent, base);
|
|
1500
1523
|
if (!isPathInside(realProjectRoot, realFull)) {
|
|
1501
|
-
throw new
|
|
1524
|
+
throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
|
|
1502
1525
|
}
|
|
1503
1526
|
return realFull;
|
|
1504
1527
|
}
|
|
@@ -1518,7 +1541,7 @@ async function realpathAllowMissing(p) {
|
|
|
1518
1541
|
while (true) {
|
|
1519
1542
|
const parent = path5.dirname(cursor);
|
|
1520
1543
|
if (parent === cursor) {
|
|
1521
|
-
throw new
|
|
1544
|
+
throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
|
|
1522
1545
|
}
|
|
1523
1546
|
segments.unshift(path5.basename(cursor));
|
|
1524
1547
|
try {
|
|
@@ -1651,7 +1674,7 @@ async function handleFilesWrite(ws, msg, projectRoot, opts = {}) {
|
|
|
1651
1674
|
return;
|
|
1652
1675
|
}
|
|
1653
1676
|
try {
|
|
1654
|
-
await
|
|
1677
|
+
await atomicWrite2(realResolved, content);
|
|
1655
1678
|
send(ws, { type: "files.written", payload: { filePath, success: true } });
|
|
1656
1679
|
if (opts.onWritten) {
|
|
1657
1680
|
void Promise.resolve(opts.onWritten(realResolved)).catch(() => void 0);
|
|
@@ -1715,10 +1738,13 @@ async function handleFilesList(ws, msg, projectRoot) {
|
|
|
1715
1738
|
payload: { files: rankFiles(results, payload.query ?? "", limit) }
|
|
1716
1739
|
});
|
|
1717
1740
|
}
|
|
1741
|
+
|
|
1742
|
+
// src/server/git-handlers.ts
|
|
1743
|
+
import nodePath from "node:path";
|
|
1718
1744
|
async function handleGitInfo(ws, projectRoot) {
|
|
1719
1745
|
const cwd = projectRoot || void 0;
|
|
1720
1746
|
try {
|
|
1721
|
-
const { execFile: ef } = await import(
|
|
1747
|
+
const { execFile: ef } = await import("node:child_process");
|
|
1722
1748
|
const git = (args) => new Promise((resolve10) => {
|
|
1723
1749
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
1724
1750
|
resolve10(err ? "" : stdout.trim());
|
|
@@ -1746,7 +1772,7 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
1746
1772
|
}
|
|
1747
1773
|
function makeGit(cwd) {
|
|
1748
1774
|
return async (args) => {
|
|
1749
|
-
const { execFile: ef } = await import(
|
|
1775
|
+
const { execFile: ef } = await import("node:child_process");
|
|
1750
1776
|
return new Promise((resolve10) => {
|
|
1751
1777
|
ef(
|
|
1752
1778
|
"git",
|
|
@@ -1776,15 +1802,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1776
1802
|
if (!m) continue;
|
|
1777
1803
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
1778
1804
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
1779
|
-
let
|
|
1780
|
-
if (
|
|
1805
|
+
let path22 = m[3] ?? "";
|
|
1806
|
+
if (path22 === "") {
|
|
1781
1807
|
i += 1;
|
|
1782
|
-
|
|
1808
|
+
path22 = parts[i + 1] ?? parts[i] ?? "";
|
|
1783
1809
|
i += 1;
|
|
1784
1810
|
}
|
|
1785
|
-
if (!
|
|
1786
|
-
const prev = counts.get(
|
|
1787
|
-
counts.set(
|
|
1811
|
+
if (!path22) continue;
|
|
1812
|
+
const prev = counts.get(path22) ?? { added: 0, deleted: 0 };
|
|
1813
|
+
counts.set(path22, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
1788
1814
|
}
|
|
1789
1815
|
};
|
|
1790
1816
|
parseNumstat(unstagedNumstat);
|
|
@@ -1796,7 +1822,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1796
1822
|
if (!rec || rec.length < 3) continue;
|
|
1797
1823
|
const x = rec[0] ?? " ";
|
|
1798
1824
|
const y = rec[1] ?? " ";
|
|
1799
|
-
const
|
|
1825
|
+
const path22 = rec.slice(3);
|
|
1800
1826
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
1801
1827
|
if (isRename) i += 1;
|
|
1802
1828
|
let status;
|
|
@@ -1808,13 +1834,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1808
1834
|
else if (x === "D" || y === "D") status = "D";
|
|
1809
1835
|
else status = "M";
|
|
1810
1836
|
const staged = x !== " " && x !== "?";
|
|
1811
|
-
let added = counts.get(
|
|
1812
|
-
let deleted = counts.get(
|
|
1837
|
+
let added = counts.get(path22)?.added ?? 0;
|
|
1838
|
+
let deleted = counts.get(path22)?.deleted ?? 0;
|
|
1813
1839
|
if (status === "?") {
|
|
1814
1840
|
added = 0;
|
|
1815
1841
|
deleted = 0;
|
|
1816
1842
|
}
|
|
1817
|
-
files.push({ path:
|
|
1843
|
+
files.push({ path: path22, status, added, deleted, staged });
|
|
1818
1844
|
}
|
|
1819
1845
|
send(ws, { type: "git.changes", payload: { files } });
|
|
1820
1846
|
} catch (err) {
|
|
@@ -1825,21 +1851,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1825
1851
|
}
|
|
1826
1852
|
}
|
|
1827
1853
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
1828
|
-
async function handleGitDiff(ws, projectRoot,
|
|
1854
|
+
async function handleGitDiff(ws, projectRoot, path22) {
|
|
1829
1855
|
const cwd = projectRoot || void 0;
|
|
1830
|
-
const reply = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
1831
|
-
if (!
|
|
1856
|
+
const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path22, ...extra } });
|
|
1857
|
+
if (!path22 || path22.includes("\0") || path22.includes("..") || nodePath.isAbsolute(path22)) {
|
|
1832
1858
|
reply({ oldText: "", newText: "", error: "invalid path" });
|
|
1833
1859
|
return;
|
|
1834
1860
|
}
|
|
1835
1861
|
try {
|
|
1836
1862
|
const git = makeGit(cwd);
|
|
1837
|
-
const { readFile: readFile10 } = await import(
|
|
1838
|
-
const { join:
|
|
1839
|
-
const oldText = await git(["show", `HEAD:${
|
|
1863
|
+
const { readFile: readFile10 } = await import("node:fs/promises");
|
|
1864
|
+
const { join: join14 } = await import("node:path");
|
|
1865
|
+
const oldText = await git(["show", `HEAD:${path22}`]);
|
|
1840
1866
|
let newText = "";
|
|
1841
1867
|
try {
|
|
1842
|
-
const abs = cwd ?
|
|
1868
|
+
const abs = cwd ? join14(cwd, path22) : path22;
|
|
1843
1869
|
const buf = await readFile10(abs);
|
|
1844
1870
|
if (buf.includes(0)) {
|
|
1845
1871
|
reply({ oldText: "", newText: "", binary: true });
|
|
@@ -1867,6 +1893,11 @@ async function handleGitDiff(ws, projectRoot, path23) {
|
|
|
1867
1893
|
}
|
|
1868
1894
|
}
|
|
1869
1895
|
|
|
1896
|
+
// src/server/http-server.ts
|
|
1897
|
+
import * as fs5 from "node:fs/promises";
|
|
1898
|
+
import * as http from "node:http";
|
|
1899
|
+
import * as path6 from "node:path";
|
|
1900
|
+
|
|
1870
1901
|
// src/server/http-server/api-handlers.ts
|
|
1871
1902
|
async function handleApiSessions(res, globalRoot) {
|
|
1872
1903
|
if (!globalRoot) {
|
|
@@ -1875,7 +1906,7 @@ async function handleApiSessions(res, globalRoot) {
|
|
|
1875
1906
|
return;
|
|
1876
1907
|
}
|
|
1877
1908
|
try {
|
|
1878
|
-
const { SessionRegistry } = await import(
|
|
1909
|
+
const { SessionRegistry } = await import("@wrongstack/core");
|
|
1879
1910
|
const registry = new SessionRegistry(globalRoot);
|
|
1880
1911
|
const sessions = await registry.list();
|
|
1881
1912
|
const result = sessions.map((s) => ({
|
|
@@ -1913,7 +1944,7 @@ async function handleApiSessionAgents(res, globalRoot, sessionId) {
|
|
|
1913
1944
|
return;
|
|
1914
1945
|
}
|
|
1915
1946
|
try {
|
|
1916
|
-
const { SessionRegistry } = await import(
|
|
1947
|
+
const { SessionRegistry } = await import("@wrongstack/core");
|
|
1917
1948
|
const registry = new SessionRegistry(globalRoot);
|
|
1918
1949
|
const entry = await registry.get(sessionId);
|
|
1919
1950
|
if (!entry) {
|
|
@@ -2053,7 +2084,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
2053
2084
|
return;
|
|
2054
2085
|
}
|
|
2055
2086
|
try {
|
|
2056
|
-
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import(
|
|
2087
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
|
|
2057
2088
|
const registry = new SessionRegistry(globalRoot);
|
|
2058
2089
|
const entry = await registry.get(sessionId);
|
|
2059
2090
|
if (!entry) {
|
|
@@ -2135,7 +2166,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
2135
2166
|
const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
|
|
2136
2167
|
const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
|
|
2137
2168
|
try {
|
|
2138
|
-
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import(
|
|
2169
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2139
2170
|
const registry = new SessionRegistry(globalRoot);
|
|
2140
2171
|
const entry = await registry.get(sessionId);
|
|
2141
2172
|
if (!entry) {
|
|
@@ -2161,7 +2192,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
|
|
|
2161
2192
|
return;
|
|
2162
2193
|
}
|
|
2163
2194
|
try {
|
|
2164
|
-
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import(
|
|
2195
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2165
2196
|
const registry = new SessionRegistry(globalRoot);
|
|
2166
2197
|
const entry = await registry.get(sessionId);
|
|
2167
2198
|
if (!entry) {
|
|
@@ -2219,7 +2250,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
2219
2250
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
2220
2251
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
2221
2252
|
try {
|
|
2222
|
-
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import(
|
|
2253
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2223
2254
|
const registry = new SessionRegistry(globalRoot);
|
|
2224
2255
|
const entry = await registry.get(sessionId);
|
|
2225
2256
|
if (!entry) {
|
|
@@ -2267,7 +2298,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
2267
2298
|
}
|
|
2268
2299
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
2269
2300
|
try {
|
|
2270
|
-
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import(
|
|
2301
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2271
2302
|
const registry = new SessionRegistry(globalRoot);
|
|
2272
2303
|
const all = await registry.list();
|
|
2273
2304
|
const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
|
|
@@ -2415,6 +2446,10 @@ async function handleApiAnalyticsSummary(res) {
|
|
|
2415
2446
|
})
|
|
2416
2447
|
);
|
|
2417
2448
|
}
|
|
2449
|
+
|
|
2450
|
+
// src/server/ws-auth.ts
|
|
2451
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
2452
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2418
2453
|
function isLoopbackHostname(hostname) {
|
|
2419
2454
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
2420
2455
|
}
|
|
@@ -2443,8 +2478,8 @@ function allowedHostname(hostname, allowedHostnames) {
|
|
|
2443
2478
|
}
|
|
2444
2479
|
function tokenMatches(provided, expected) {
|
|
2445
2480
|
if (!provided) return false;
|
|
2446
|
-
const a =
|
|
2447
|
-
const b =
|
|
2481
|
+
const a = Buffer2.from(provided);
|
|
2482
|
+
const b = Buffer2.from(expected);
|
|
2448
2483
|
if (a.length !== b.length) return false;
|
|
2449
2484
|
return timingSafeEqual(a, b);
|
|
2450
2485
|
}
|
|
@@ -2600,9 +2635,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
|
2600
2635
|
return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
2601
2636
|
}
|
|
2602
2637
|
function isInsideDist(candidate, distDir) {
|
|
2603
|
-
const root =
|
|
2604
|
-
const resolved =
|
|
2605
|
-
return resolved === root || resolved.startsWith(root +
|
|
2638
|
+
const root = path6.resolve(distDir);
|
|
2639
|
+
const resolved = path6.resolve(candidate);
|
|
2640
|
+
return resolved === root || resolved.startsWith(root + path6.sep);
|
|
2606
2641
|
}
|
|
2607
2642
|
function decodeSessionId(segment) {
|
|
2608
2643
|
try {
|
|
@@ -2613,7 +2648,7 @@ function decodeSessionId(segment) {
|
|
|
2613
2648
|
}
|
|
2614
2649
|
function createHttpServer(opts) {
|
|
2615
2650
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
2616
|
-
const distDir =
|
|
2651
|
+
const distDir = path6.resolve(opts.distDir);
|
|
2617
2652
|
const wsPort = opts.wsPort;
|
|
2618
2653
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
2619
2654
|
return http.createServer(async (req, res) => {
|
|
@@ -2790,21 +2825,21 @@ function createHttpServer(opts) {
|
|
|
2790
2825
|
}
|
|
2791
2826
|
let filePath;
|
|
2792
2827
|
if (url.pathname === "/" || url.pathname === "") {
|
|
2793
|
-
filePath =
|
|
2828
|
+
filePath = path6.join(distDir, "index.html");
|
|
2794
2829
|
} else if (url.pathname.startsWith("/assets/")) {
|
|
2795
|
-
filePath =
|
|
2830
|
+
filePath = path6.join(distDir, url.pathname);
|
|
2796
2831
|
} else if (url.pathname.startsWith("/")) {
|
|
2797
|
-
filePath =
|
|
2832
|
+
filePath = path6.join(distDir, url.pathname);
|
|
2798
2833
|
} else {
|
|
2799
|
-
filePath =
|
|
2834
|
+
filePath = path6.join(distDir, "index.html");
|
|
2800
2835
|
}
|
|
2801
|
-
const resolvedPath =
|
|
2836
|
+
const resolvedPath = path6.resolve(filePath);
|
|
2802
2837
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
2803
2838
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
2804
2839
|
res.end("Forbidden");
|
|
2805
2840
|
return;
|
|
2806
2841
|
}
|
|
2807
|
-
const ext =
|
|
2842
|
+
const ext = path6.extname(resolvedPath);
|
|
2808
2843
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2809
2844
|
res.setHeader("Content-Type", contentType);
|
|
2810
2845
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
@@ -2816,18 +2851,18 @@ function createHttpServer(opts) {
|
|
|
2816
2851
|
"Content-Security-Policy",
|
|
2817
2852
|
buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
|
|
2818
2853
|
);
|
|
2819
|
-
const html = await
|
|
2854
|
+
const html = await fs5.readFile(resolvedPath, "utf8");
|
|
2820
2855
|
res.writeHead(200);
|
|
2821
2856
|
res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
|
|
2822
2857
|
return;
|
|
2823
2858
|
}
|
|
2824
|
-
const fileContent = await
|
|
2859
|
+
const fileContent = await fs5.readFile(resolvedPath);
|
|
2825
2860
|
res.writeHead(200);
|
|
2826
2861
|
res.end(fileContent);
|
|
2827
2862
|
} catch (err) {
|
|
2828
2863
|
if (err.code === "ENOENT") {
|
|
2829
2864
|
try {
|
|
2830
|
-
const html = await
|
|
2865
|
+
const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
|
|
2831
2866
|
res.writeHead(200, {
|
|
2832
2867
|
"Content-Type": "text/html",
|
|
2833
2868
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -2851,11 +2886,17 @@ function createHttpServer(opts) {
|
|
|
2851
2886
|
}
|
|
2852
2887
|
});
|
|
2853
2888
|
}
|
|
2889
|
+
|
|
2890
|
+
// src/server/instance-registry.ts
|
|
2891
|
+
import * as os from "node:os";
|
|
2892
|
+
import * as path7 from "node:path";
|
|
2893
|
+
import * as fs6 from "node:fs/promises";
|
|
2894
|
+
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
2854
2895
|
function defaultBaseDir() {
|
|
2855
|
-
return
|
|
2896
|
+
return path7.join(os.homedir(), ".wrongstack");
|
|
2856
2897
|
}
|
|
2857
2898
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
2858
|
-
return
|
|
2899
|
+
return path7.join(baseDir, "webui-instances.json");
|
|
2859
2900
|
}
|
|
2860
2901
|
function isPidAlive(pid) {
|
|
2861
2902
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -2868,7 +2909,7 @@ function isPidAlive(pid) {
|
|
|
2868
2909
|
}
|
|
2869
2910
|
async function load(file) {
|
|
2870
2911
|
try {
|
|
2871
|
-
const raw = await
|
|
2912
|
+
const raw = await fs6.readFile(file, "utf8");
|
|
2872
2913
|
const parsed = JSON.parse(raw);
|
|
2873
2914
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
2874
2915
|
return parsed;
|
|
@@ -2878,7 +2919,7 @@ async function load(file) {
|
|
|
2878
2919
|
return { version: 1, instances: [] };
|
|
2879
2920
|
}
|
|
2880
2921
|
async function save(file, instances) {
|
|
2881
|
-
await
|
|
2922
|
+
await atomicWrite3(file, `${JSON.stringify({ version: 1, instances }, null, 2)}
|
|
2882
2923
|
`, {
|
|
2883
2924
|
mode: 384
|
|
2884
2925
|
});
|
|
@@ -2923,6 +2964,19 @@ function formatInstances(instances) {
|
|
|
2923
2964
|
}
|
|
2924
2965
|
return lines.join("\n");
|
|
2925
2966
|
}
|
|
2967
|
+
|
|
2968
|
+
// src/server/mcp-handlers.ts
|
|
2969
|
+
import { allServers } from "@wrongstack/core";
|
|
2970
|
+
import {
|
|
2971
|
+
addMcp,
|
|
2972
|
+
disableMcp,
|
|
2973
|
+
discoverMcp,
|
|
2974
|
+
enableMcp,
|
|
2975
|
+
listMcp,
|
|
2976
|
+
removeMcp,
|
|
2977
|
+
restartMcp,
|
|
2978
|
+
updateMcp
|
|
2979
|
+
} from "@wrongstack/mcp";
|
|
2926
2980
|
function mapStatus(raw) {
|
|
2927
2981
|
switch (raw) {
|
|
2928
2982
|
case "connected":
|
|
@@ -2938,7 +2992,7 @@ function mapStatus(raw) {
|
|
|
2938
2992
|
return "stopped";
|
|
2939
2993
|
}
|
|
2940
2994
|
}
|
|
2941
|
-
function toView(info) {
|
|
2995
|
+
function toView(info, health) {
|
|
2942
2996
|
const view = {
|
|
2943
2997
|
name: info.name,
|
|
2944
2998
|
transport: info.transport,
|
|
@@ -2950,6 +3004,7 @@ function toView(info) {
|
|
|
2950
3004
|
};
|
|
2951
3005
|
if (info.description !== void 0) view.description = info.description;
|
|
2952
3006
|
if (info.lazy !== void 0) view.lazy = info.lazy;
|
|
3007
|
+
if (health !== void 0) view.health = health;
|
|
2953
3008
|
return view;
|
|
2954
3009
|
}
|
|
2955
3010
|
function deps(ws, globalConfigPath, registry) {
|
|
@@ -2975,7 +3030,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
|
|
|
2975
3030
|
registry: mcpRegistry,
|
|
2976
3031
|
presets: allServers()
|
|
2977
3032
|
});
|
|
2978
|
-
|
|
3033
|
+
const health = new Map(
|
|
3034
|
+
(typeof mcpRegistry.operationalHealth === "function" ? mcpRegistry.operationalHealth() : []).map((item) => [item.name, item])
|
|
3035
|
+
);
|
|
3036
|
+
send(ws, {
|
|
3037
|
+
type: "mcp.list",
|
|
3038
|
+
payload: { servers: servers.map((server) => toView(server, health.get(server.name))) }
|
|
3039
|
+
});
|
|
2979
3040
|
}
|
|
2980
3041
|
async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
|
|
2981
3042
|
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
@@ -3125,6 +3186,99 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
|
|
|
3125
3186
|
payload: { success: result.ok, message: result.message }
|
|
3126
3187
|
});
|
|
3127
3188
|
}
|
|
3189
|
+
async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3190
|
+
if (!mcpRegistry) return sendContentError(ws, "resources", "", "MCP registry is not available.");
|
|
3191
|
+
const payload = payloadRecord(msg);
|
|
3192
|
+
let serverName = "";
|
|
3193
|
+
try {
|
|
3194
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3195
|
+
const refresh = payload["refresh"] === true;
|
|
3196
|
+
const [resources, resourceTemplates] = await Promise.all([
|
|
3197
|
+
mcpRegistry.listResources(serverName, { refresh }),
|
|
3198
|
+
mcpRegistry.listResourceTemplates(serverName, { refresh })
|
|
3199
|
+
]);
|
|
3200
|
+
send(ws, {
|
|
3201
|
+
type: "mcp.resources",
|
|
3202
|
+
payload: { name: serverName, resources, resourceTemplates }
|
|
3203
|
+
});
|
|
3204
|
+
} catch (err) {
|
|
3205
|
+
sendContentError(ws, "resources", serverName, errorMessage(err));
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3209
|
+
if (!mcpRegistry) return sendContentError(ws, "prompts", "", "MCP registry is not available.");
|
|
3210
|
+
const payload = payloadRecord(msg);
|
|
3211
|
+
let serverName = "";
|
|
3212
|
+
try {
|
|
3213
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3214
|
+
const prompts = await mcpRegistry.listPrompts(serverName, {
|
|
3215
|
+
refresh: payload["refresh"] === true
|
|
3216
|
+
});
|
|
3217
|
+
send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
|
|
3218
|
+
} catch (err) {
|
|
3219
|
+
sendContentError(ws, "prompts", serverName, errorMessage(err));
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3223
|
+
if (!mcpRegistry)
|
|
3224
|
+
return sendContentError(ws, "resource.read", "", "MCP registry is not available.");
|
|
3225
|
+
const payload = payloadRecord(msg);
|
|
3226
|
+
let serverName = "";
|
|
3227
|
+
try {
|
|
3228
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3229
|
+
const insertion = await mcpRegistry.selectResourceForInsertion(
|
|
3230
|
+
serverName,
|
|
3231
|
+
requiredPayloadString(payload, "uri")
|
|
3232
|
+
);
|
|
3233
|
+
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3234
|
+
} catch (err) {
|
|
3235
|
+
sendContentError(ws, "resource.read", serverName, errorMessage(err));
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3239
|
+
if (!mcpRegistry) return sendContentError(ws, "prompt.get", "", "MCP registry is not available.");
|
|
3240
|
+
const payload = payloadRecord(msg);
|
|
3241
|
+
let serverName = "";
|
|
3242
|
+
try {
|
|
3243
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3244
|
+
const insertion = await mcpRegistry.selectPromptForInsertion(
|
|
3245
|
+
serverName,
|
|
3246
|
+
requiredPayloadString(payload, "prompt"),
|
|
3247
|
+
promptArguments(payload["arguments"])
|
|
3248
|
+
);
|
|
3249
|
+
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3250
|
+
} catch (err) {
|
|
3251
|
+
sendContentError(ws, "prompt.get", serverName, errorMessage(err));
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
function payloadRecord(msg) {
|
|
3255
|
+
return msg.payload && typeof msg.payload === "object" && !Array.isArray(msg.payload) ? msg.payload : {};
|
|
3256
|
+
}
|
|
3257
|
+
function requiredPayloadString(payload, field) {
|
|
3258
|
+
const value = payload[field];
|
|
3259
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
3260
|
+
throw new Error(`MCP payload field "${field}" must be a non-empty string`);
|
|
3261
|
+
}
|
|
3262
|
+
return value;
|
|
3263
|
+
}
|
|
3264
|
+
function promptArguments(value) {
|
|
3265
|
+
if (value === void 0) return void 0;
|
|
3266
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3267
|
+
throw new Error("MCP prompt arguments must be an object");
|
|
3268
|
+
}
|
|
3269
|
+
const args = {};
|
|
3270
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3271
|
+
if (typeof item !== "string") throw new Error(`MCP prompt argument "${key}" must be a string`);
|
|
3272
|
+
args[key] = item;
|
|
3273
|
+
}
|
|
3274
|
+
return args;
|
|
3275
|
+
}
|
|
3276
|
+
function sendContentError(ws, action, name2, error) {
|
|
3277
|
+
send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
|
|
3278
|
+
}
|
|
3279
|
+
function errorMessage(err) {
|
|
3280
|
+
return err instanceof Error ? err.message : String(err);
|
|
3281
|
+
}
|
|
3128
3282
|
|
|
3129
3283
|
// src/server/memory-handlers.ts
|
|
3130
3284
|
async function handleMemoryList(ws, memoryStore) {
|
|
@@ -3160,6 +3314,9 @@ async function handleMemoryForget(ws, msg, memoryStore) {
|
|
|
3160
3314
|
sendResult(ws, false, errMessage(err));
|
|
3161
3315
|
}
|
|
3162
3316
|
}
|
|
3317
|
+
|
|
3318
|
+
// src/server/open-browser.ts
|
|
3319
|
+
import { spawn } from "node:child_process";
|
|
3163
3320
|
function browserOpenCommand(url, platform = process.platform) {
|
|
3164
3321
|
if (platform === "win32") {
|
|
3165
3322
|
return { command: "cmd", args: ["/c", "start", "", url] };
|
|
@@ -3178,7 +3335,7 @@ function openBrowser(url, platform = process.platform) {
|
|
|
3178
3335
|
child.unref();
|
|
3179
3336
|
if (child.pid) {
|
|
3180
3337
|
try {
|
|
3181
|
-
import(
|
|
3338
|
+
import("@wrongstack/tools").then(({ getProcessRegistry }) => {
|
|
3182
3339
|
const pid = child.pid;
|
|
3183
3340
|
if (pid === void 0) return;
|
|
3184
3341
|
getProcessRegistry().register({
|
|
@@ -3200,6 +3357,10 @@ function openBrowser(url, platform = process.platform) {
|
|
|
3200
3357
|
} catch {
|
|
3201
3358
|
}
|
|
3202
3359
|
}
|
|
3360
|
+
|
|
3361
|
+
// src/server/port-utils.ts
|
|
3362
|
+
import * as net from "node:net";
|
|
3363
|
+
import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
3203
3364
|
function isPortFree(host, port) {
|
|
3204
3365
|
return new Promise((resolve10) => {
|
|
3205
3366
|
const srv = net.createServer();
|
|
@@ -3225,7 +3386,7 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
3225
3386
|
}
|
|
3226
3387
|
port++;
|
|
3227
3388
|
}
|
|
3228
|
-
throw new
|
|
3389
|
+
throw new ToolValidationError3({
|
|
3229
3390
|
message: `No free port found near ${startPort} on ${host} after ${maxTries} attempts.`,
|
|
3230
3391
|
field: "port"
|
|
3231
3392
|
});
|
|
@@ -3435,10 +3596,15 @@ async function handlePromptsRecent(ws, ctx) {
|
|
|
3435
3596
|
send(ws, { type: "prompts.recent", payload: { slugs: [], error: errMessage(err) } });
|
|
3436
3597
|
}
|
|
3437
3598
|
}
|
|
3599
|
+
|
|
3600
|
+
// src/server/provider-config-io.ts
|
|
3601
|
+
import * as fs7 from "node:fs/promises";
|
|
3602
|
+
import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
3603
|
+
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
3438
3604
|
async function loadSavedProviders(configPath, vault) {
|
|
3439
3605
|
let raw;
|
|
3440
3606
|
try {
|
|
3441
|
-
raw = await
|
|
3607
|
+
raw = await fs7.readFile(configPath, "utf8");
|
|
3442
3608
|
} catch {
|
|
3443
3609
|
return {};
|
|
3444
3610
|
}
|
|
@@ -3455,7 +3621,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
3455
3621
|
let raw;
|
|
3456
3622
|
let fileExists = true;
|
|
3457
3623
|
try {
|
|
3458
|
-
raw = await
|
|
3624
|
+
raw = await fs7.readFile(configPath, "utf8");
|
|
3459
3625
|
} catch (err) {
|
|
3460
3626
|
if (err.code !== "ENOENT") {
|
|
3461
3627
|
throw new ConfigError({
|
|
@@ -3484,8 +3650,11 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
3484
3650
|
}
|
|
3485
3651
|
parsed.providers = providers;
|
|
3486
3652
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
3487
|
-
await
|
|
3653
|
+
await atomicWrite4(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3488
3654
|
}
|
|
3655
|
+
|
|
3656
|
+
// src/server/provider-keys.ts
|
|
3657
|
+
import { expectDefined } from "@wrongstack/core";
|
|
3489
3658
|
function normalizeKeys(cfg) {
|
|
3490
3659
|
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
3491
3660
|
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
@@ -3579,6 +3748,12 @@ function removeProvider(providers, providerId) {
|
|
|
3579
3748
|
delete providers[providerId];
|
|
3580
3749
|
return { ok: true, message: `Provider "${providerId}" removed` };
|
|
3581
3750
|
}
|
|
3751
|
+
|
|
3752
|
+
// src/server/sdd-board-ws-handler.ts
|
|
3753
|
+
import {
|
|
3754
|
+
applySddLifecycle,
|
|
3755
|
+
SddBoardStore
|
|
3756
|
+
} from "@wrongstack/sdd";
|
|
3582
3757
|
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
3583
3758
|
"pause",
|
|
3584
3759
|
"resume",
|
|
@@ -3723,6 +3898,26 @@ var SddBoardWebSocketHandler = class {
|
|
|
3723
3898
|
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3724
3899
|
}
|
|
3725
3900
|
};
|
|
3901
|
+
|
|
3902
|
+
// src/server/sdd-wizard-wiring.ts
|
|
3903
|
+
import * as path8 from "node:path";
|
|
3904
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3905
|
+
import {
|
|
3906
|
+
WorktreeManager as WorktreeManager2,
|
|
3907
|
+
ToolValidationError as ToolValidationError4
|
|
3908
|
+
} from "@wrongstack/core";
|
|
3909
|
+
import {
|
|
3910
|
+
cleanupStaleSddWorktrees,
|
|
3911
|
+
makeCommandVerifier,
|
|
3912
|
+
makeLlmSubtaskGenerator,
|
|
3913
|
+
SddBoardStore as SddBoardStore2,
|
|
3914
|
+
SddInterviewDriver,
|
|
3915
|
+
SddRunRegistry,
|
|
3916
|
+
SddSupervisor,
|
|
3917
|
+
SpecStore,
|
|
3918
|
+
startSddRun,
|
|
3919
|
+
TaskGraphStore
|
|
3920
|
+
} from "@wrongstack/sdd";
|
|
3726
3921
|
var PLANNING_ONLY_GUARD = "SYSTEM: You are running a PLANNING-ONLY specification interview. Do NOT write, create, or edit any files, and do NOT run shell/terminal commands or use any code-editing tools \u2014 they are disabled here and any attempt will fail and waste the turn. Respond with TEXT ONLY: ask your question, or emit the requested spec / plan / task JSON. All code is written later, automatically, once the plan is approved and the multi-agent run starts.\n\n---\n\n";
|
|
3727
3922
|
function buildSddWizardDeps(opts) {
|
|
3728
3923
|
const registry = new SddRunRegistry();
|
|
@@ -3746,14 +3941,14 @@ function buildSddWizardDeps(opts) {
|
|
|
3746
3941
|
makeDriver: () => new SddInterviewDriver({
|
|
3747
3942
|
specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
|
|
3748
3943
|
graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
|
|
3749
|
-
sessionPath:
|
|
3944
|
+
sessionPath: path8.join(opts.paths.projectDir, "sdd-wizard-session.json")
|
|
3750
3945
|
}),
|
|
3751
3946
|
runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
|
|
3752
3947
|
startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
|
|
3753
3948
|
const graph = driver.getGraph();
|
|
3754
3949
|
const tracker = driver.getTracker();
|
|
3755
3950
|
if (!graph || !tracker) {
|
|
3756
|
-
throw new
|
|
3951
|
+
throw new ToolValidationError4({
|
|
3757
3952
|
message: "No task graph to run \u2014 finish the interview first.",
|
|
3758
3953
|
field: "taskGraph"
|
|
3759
3954
|
});
|
|
@@ -3761,7 +3956,7 @@ function buildSddWizardDeps(opts) {
|
|
|
3761
3956
|
const worktreesEnabled = useWorktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
|
|
3762
3957
|
let worktrees;
|
|
3763
3958
|
if (worktreesEnabled) {
|
|
3764
|
-
const inGit =
|
|
3959
|
+
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3765
3960
|
cwd: opts.projectRoot,
|
|
3766
3961
|
encoding: "utf8",
|
|
3767
3962
|
windowsHide: true
|
|
@@ -3771,14 +3966,14 @@ function buildSddWizardDeps(opts) {
|
|
|
3771
3966
|
projectRoot: opts.projectRoot,
|
|
3772
3967
|
boardsDir: opts.paths.projectSddBoards
|
|
3773
3968
|
}).catch(() => void 0);
|
|
3774
|
-
worktrees = new
|
|
3969
|
+
worktrees = new WorktreeManager2({
|
|
3775
3970
|
projectRoot: opts.projectRoot,
|
|
3776
3971
|
events: opts.events,
|
|
3777
3972
|
sessionId: () => opts.agent.ctx.session?.id
|
|
3778
3973
|
});
|
|
3779
3974
|
}
|
|
3780
3975
|
}
|
|
3781
|
-
const boardStore = new
|
|
3976
|
+
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3782
3977
|
const verifyTask = makeCommandVerifier();
|
|
3783
3978
|
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3784
3979
|
brain: opts.brain,
|
|
@@ -3958,20 +4153,25 @@ var SddWizardWebSocketHandler = class {
|
|
|
3958
4153
|
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3959
4154
|
}
|
|
3960
4155
|
};
|
|
4156
|
+
|
|
4157
|
+
// src/server/shell-open.ts
|
|
4158
|
+
import * as fs8 from "node:fs/promises";
|
|
4159
|
+
import * as path9 from "node:path";
|
|
4160
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
3961
4161
|
var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
|
|
3962
4162
|
function shellQuote(s) {
|
|
3963
4163
|
return s.replaceAll("'", `'"'"'`);
|
|
3964
4164
|
}
|
|
3965
4165
|
async function handleShellOpen(req, logger) {
|
|
3966
4166
|
try {
|
|
3967
|
-
const resolved =
|
|
3968
|
-
await
|
|
4167
|
+
const resolved = path9.resolve(req.path);
|
|
4168
|
+
await fs8.access(resolved);
|
|
3969
4169
|
if (METACHAR_REGEX.test(resolved)) {
|
|
3970
4170
|
return { success: false, message: "Path contains unsupported characters." };
|
|
3971
4171
|
}
|
|
3972
4172
|
const platform = process.platform;
|
|
3973
4173
|
const launch = (cmd, args, onError) => {
|
|
3974
|
-
const child =
|
|
4174
|
+
const child = spawn2(cmd, args, {
|
|
3975
4175
|
detached: true,
|
|
3976
4176
|
stdio: "ignore",
|
|
3977
4177
|
windowsHide: true
|
|
@@ -4015,6 +4215,12 @@ async function handleShellOpen(req, logger) {
|
|
|
4015
4215
|
}
|
|
4016
4216
|
}
|
|
4017
4217
|
|
|
4218
|
+
// src/server/skills-handlers.ts
|
|
4219
|
+
import { promises as fs9 } from "node:fs";
|
|
4220
|
+
import path10 from "node:path";
|
|
4221
|
+
import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
|
|
4222
|
+
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
4223
|
+
|
|
4018
4224
|
// src/server/ws-payload-validation.ts
|
|
4019
4225
|
function isRecord(value) {
|
|
4020
4226
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -4148,6 +4354,16 @@ function validateBrainAskPayload(payload) {
|
|
|
4148
4354
|
}
|
|
4149
4355
|
return { ok: true, value: { question: question.trim() } };
|
|
4150
4356
|
}
|
|
4357
|
+
function validateBrainConfigSetPayload(payload) {
|
|
4358
|
+
if (!isRecord(payload)) {
|
|
4359
|
+
return { ok: false, message: "brain.config.set payload must be an object with a patch object" };
|
|
4360
|
+
}
|
|
4361
|
+
const patch = payload["patch"];
|
|
4362
|
+
if (!isRecord(patch)) {
|
|
4363
|
+
return { ok: false, message: "brain.config.set payload.patch must be an object" };
|
|
4364
|
+
}
|
|
4365
|
+
return { ok: true, value: { patch } };
|
|
4366
|
+
}
|
|
4151
4367
|
function validateAutonomySwitchPayload(payload) {
|
|
4152
4368
|
if (!isRecord(payload)) {
|
|
4153
4369
|
return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
|
|
@@ -4209,7 +4425,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4209
4425
|
"hqEnabled",
|
|
4210
4426
|
"hqRawContent",
|
|
4211
4427
|
"fallbackAuto",
|
|
4212
|
-
"favoriteModelsOnly"
|
|
4428
|
+
"favoriteModelsOnly",
|
|
4429
|
+
"breakerEnabled",
|
|
4430
|
+
"debugStream"
|
|
4213
4431
|
]);
|
|
4214
4432
|
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
|
|
4215
4433
|
var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
|
|
@@ -4220,9 +4438,17 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4220
4438
|
"maxIterations",
|
|
4221
4439
|
"maxConcurrent",
|
|
4222
4440
|
"enhanceDelayMs",
|
|
4223
|
-
"tgLongToolMs"
|
|
4441
|
+
"tgLongToolMs",
|
|
4442
|
+
"breakerAutoKillResetMs"
|
|
4443
|
+
]);
|
|
4444
|
+
var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
4445
|
+
"hqUrl",
|
|
4446
|
+
"hqToken",
|
|
4447
|
+
"uiLocale",
|
|
4448
|
+
"thinkingWord",
|
|
4449
|
+
"refinerProvider",
|
|
4450
|
+
"refinerModel"
|
|
4224
4451
|
]);
|
|
4225
|
-
var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
|
|
4226
4452
|
var ENUM_PREF_KEYS = {
|
|
4227
4453
|
autonomy: AUTONOMY_VALUES,
|
|
4228
4454
|
contextStrategy: CONTEXT_STRATEGY_VALUES,
|
|
@@ -4233,36 +4459,39 @@ var ENUM_PREF_KEYS = {
|
|
|
4233
4459
|
auditLevel: AUDIT_LEVEL_VALUES,
|
|
4234
4460
|
reasoningMode: REASONING_MODE_VALUES,
|
|
4235
4461
|
reasoningEffort: REASONING_EFFORT_VALUES,
|
|
4236
|
-
cacheTtl: CACHE_TTL_VALUES
|
|
4462
|
+
cacheTtl: CACHE_TTL_VALUES,
|
|
4463
|
+
statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
|
|
4464
|
+
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
|
|
4465
|
+
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
|
|
4237
4466
|
};
|
|
4238
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
4467
|
+
function validateModelRuntimeValue(modelRuntime, path22) {
|
|
4239
4468
|
const reasoning = modelRuntime["reasoning"];
|
|
4240
4469
|
if (reasoning !== void 0) {
|
|
4241
|
-
if (!isRecord(reasoning)) return `${
|
|
4470
|
+
if (!isRecord(reasoning)) return `${path22}.reasoning must be an object when provided`;
|
|
4242
4471
|
const mode = reasoning["mode"];
|
|
4243
4472
|
const effort = reasoning["effort"];
|
|
4244
4473
|
const preserve = reasoning["preserve"];
|
|
4245
4474
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
4246
|
-
return `${
|
|
4475
|
+
return `${path22}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
4247
4476
|
}
|
|
4248
4477
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
4249
|
-
return `${
|
|
4478
|
+
return `${path22}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
4250
4479
|
}
|
|
4251
4480
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
4252
|
-
return `${
|
|
4481
|
+
return `${path22}.reasoning.preserve must be a boolean when provided`;
|
|
4253
4482
|
}
|
|
4254
4483
|
}
|
|
4255
4484
|
const cache = modelRuntime["cache"];
|
|
4256
4485
|
if (cache !== void 0) {
|
|
4257
|
-
if (!isRecord(cache)) return `${
|
|
4486
|
+
if (!isRecord(cache)) return `${path22}.cache must be an object when provided`;
|
|
4258
4487
|
const ttl = cache["ttl"];
|
|
4259
4488
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
4260
|
-
return `${
|
|
4489
|
+
return `${path22}.cache.ttl must be one of: 5m, 1h`;
|
|
4261
4490
|
}
|
|
4262
4491
|
}
|
|
4263
4492
|
const parameters = modelRuntime["parameters"];
|
|
4264
4493
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
4265
|
-
return `${
|
|
4494
|
+
return `${path22}.parameters must be an object when provided`;
|
|
4266
4495
|
}
|
|
4267
4496
|
return null;
|
|
4268
4497
|
}
|
|
@@ -4542,8 +4771,8 @@ function validateShellOpenPayload(payload) {
|
|
|
4542
4771
|
if (!isRecord(payload)) {
|
|
4543
4772
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
4544
4773
|
}
|
|
4545
|
-
const
|
|
4546
|
-
if (typeof
|
|
4774
|
+
const path22 = payload["path"];
|
|
4775
|
+
if (typeof path22 !== "string" || path22.trim().length === 0) {
|
|
4547
4776
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
4548
4777
|
}
|
|
4549
4778
|
const target = payload["target"];
|
|
@@ -4556,7 +4785,7 @@ function validateShellOpenPayload(payload) {
|
|
|
4556
4785
|
return {
|
|
4557
4786
|
ok: true,
|
|
4558
4787
|
value: {
|
|
4559
|
-
path:
|
|
4788
|
+
path: path22,
|
|
4560
4789
|
...target !== void 0 ? { target } : {}
|
|
4561
4790
|
}
|
|
4562
4791
|
};
|
|
@@ -4565,14 +4794,89 @@ function validateGitDiffPayload(payload) {
|
|
|
4565
4794
|
if (!isRecord(payload)) {
|
|
4566
4795
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
4567
4796
|
}
|
|
4568
|
-
const
|
|
4569
|
-
if (
|
|
4797
|
+
const path22 = payload["path"];
|
|
4798
|
+
if (path22 === void 0 || path22 === null) {
|
|
4570
4799
|
return { ok: true, value: { path: "" } };
|
|
4571
4800
|
}
|
|
4572
|
-
if (typeof
|
|
4801
|
+
if (typeof path22 !== "string") {
|
|
4573
4802
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
4574
4803
|
}
|
|
4575
|
-
return { ok: true, value: { path:
|
|
4804
|
+
return { ok: true, value: { path: path22 } };
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
// src/server/zip.ts
|
|
4808
|
+
import { deflateRawSync, inflateRawSync } from "node:zlib";
|
|
4809
|
+
function createZipBuffer(entries) {
|
|
4810
|
+
const localParts = [];
|
|
4811
|
+
const centralParts = [];
|
|
4812
|
+
let offset = 0;
|
|
4813
|
+
const { date, time } = dosDateTime(/* @__PURE__ */ new Date());
|
|
4814
|
+
for (const entry of entries) {
|
|
4815
|
+
const name2 = Buffer.from(normalizeZipPath(entry.name), "utf8");
|
|
4816
|
+
const data = typeof entry.data === "string" ? Buffer.from(entry.data, "utf8") : Buffer.from(entry.data);
|
|
4817
|
+
const compressed = deflateRawSync(data);
|
|
4818
|
+
const crc = crc32(data);
|
|
4819
|
+
const local = Buffer.alloc(30);
|
|
4820
|
+
local.writeUInt32LE(67324752, 0);
|
|
4821
|
+
local.writeUInt16LE(20, 4);
|
|
4822
|
+
local.writeUInt16LE(2048, 6);
|
|
4823
|
+
local.writeUInt16LE(8, 8);
|
|
4824
|
+
local.writeUInt16LE(time, 10);
|
|
4825
|
+
local.writeUInt16LE(date, 12);
|
|
4826
|
+
local.writeUInt32LE(crc, 14);
|
|
4827
|
+
local.writeUInt32LE(compressed.length, 18);
|
|
4828
|
+
local.writeUInt32LE(data.length, 22);
|
|
4829
|
+
local.writeUInt16LE(name2.length, 26);
|
|
4830
|
+
localParts.push(local, name2, compressed);
|
|
4831
|
+
const central = Buffer.alloc(46);
|
|
4832
|
+
central.writeUInt32LE(33639248, 0);
|
|
4833
|
+
central.writeUInt16LE(798, 4);
|
|
4834
|
+
central.writeUInt16LE(20, 6);
|
|
4835
|
+
central.writeUInt16LE(2048, 8);
|
|
4836
|
+
central.writeUInt16LE(8, 10);
|
|
4837
|
+
central.writeUInt16LE(time, 12);
|
|
4838
|
+
central.writeUInt16LE(date, 14);
|
|
4839
|
+
central.writeUInt32LE(crc, 16);
|
|
4840
|
+
central.writeUInt32LE(compressed.length, 20);
|
|
4841
|
+
central.writeUInt32LE(data.length, 24);
|
|
4842
|
+
central.writeUInt16LE(name2.length, 28);
|
|
4843
|
+
central.writeUInt32LE(offset, 42);
|
|
4844
|
+
centralParts.push(central, name2);
|
|
4845
|
+
offset += local.length + name2.length + compressed.length;
|
|
4846
|
+
}
|
|
4847
|
+
const centralDirectory = Buffer.concat(centralParts);
|
|
4848
|
+
const end = Buffer.alloc(22);
|
|
4849
|
+
end.writeUInt32LE(101010256, 0);
|
|
4850
|
+
end.writeUInt16LE(entries.length, 8);
|
|
4851
|
+
end.writeUInt16LE(entries.length, 10);
|
|
4852
|
+
end.writeUInt32LE(centralDirectory.length, 12);
|
|
4853
|
+
end.writeUInt32LE(offset, 16);
|
|
4854
|
+
return Buffer.concat([...localParts, centralDirectory, end]);
|
|
4855
|
+
}
|
|
4856
|
+
function normalizeZipPath(value) {
|
|
4857
|
+
const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
4858
|
+
if (!isSafeZipPath(normalized)) throw new Error(`Unsafe ZIP entry path: ${value}`);
|
|
4859
|
+
return normalized;
|
|
4860
|
+
}
|
|
4861
|
+
function isSafeZipPath(value) {
|
|
4862
|
+
return value.length > 0 && !value.includes("\0") && !value.startsWith("/") && !/^[a-z]:/i.test(value) && !value.split("/").includes("..");
|
|
4863
|
+
}
|
|
4864
|
+
function dosDateTime(value) {
|
|
4865
|
+
const year = Math.max(1980, Math.min(2107, value.getFullYear()));
|
|
4866
|
+
return {
|
|
4867
|
+
date: year - 1980 << 9 | value.getMonth() + 1 << 5 | value.getDate(),
|
|
4868
|
+
time: value.getHours() << 11 | value.getMinutes() << 5 | Math.floor(value.getSeconds() / 2)
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
var CRC_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
4872
|
+
let crc = index;
|
|
4873
|
+
for (let bit = 0; bit < 8; bit++) crc = (crc & 1) !== 0 ? 3988292384 ^ crc >>> 1 : crc >>> 1;
|
|
4874
|
+
return crc >>> 0;
|
|
4875
|
+
});
|
|
4876
|
+
function crc32(data) {
|
|
4877
|
+
let crc = 4294967295;
|
|
4878
|
+
for (const byte of data) crc = crc >>> 8 ^ (CRC_TABLE[(crc ^ byte) & 255] ?? 0);
|
|
4879
|
+
return (crc ^ 4294967295) >>> 0;
|
|
4576
4880
|
}
|
|
4577
4881
|
|
|
4578
4882
|
// src/server/skills-handlers.ts
|
|
@@ -4643,19 +4947,19 @@ async function handleSkillsContent(ws, ctx, msg) {
|
|
|
4643
4947
|
send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
|
|
4644
4948
|
return;
|
|
4645
4949
|
}
|
|
4646
|
-
const body = await
|
|
4647
|
-
const skillDir =
|
|
4950
|
+
const body = await fs9.readFile(entry.path, "utf8");
|
|
4951
|
+
const skillDir = path10.dirname(entry.path);
|
|
4648
4952
|
let relatedFiles = [];
|
|
4649
4953
|
try {
|
|
4650
|
-
const files = await
|
|
4651
|
-
relatedFiles = files.filter((f) => f !==
|
|
4954
|
+
const files = await fs9.readdir(skillDir);
|
|
4955
|
+
relatedFiles = files.filter((f) => f !== path10.basename(entry.path)).map((f) => path10.join(skillDir, f));
|
|
4652
4956
|
} catch {
|
|
4653
4957
|
}
|
|
4654
4958
|
const nameLower = name2.toLowerCase();
|
|
4655
4959
|
const refResults = await Promise.all(
|
|
4656
4960
|
entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
|
|
4657
4961
|
try {
|
|
4658
|
-
const content = await
|
|
4962
|
+
const content = await fs9.readFile(e.path, "utf8");
|
|
4659
4963
|
return [e.name, content.toLowerCase().includes(nameLower)];
|
|
4660
4964
|
} catch {
|
|
4661
4965
|
return [e.name, false];
|
|
@@ -4745,20 +5049,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
|
|
|
4745
5049
|
}
|
|
4746
5050
|
const createPayload = parsed.value;
|
|
4747
5051
|
try {
|
|
4748
|
-
const targetDir = createPayload.scope === "global" ?
|
|
4749
|
-
ctx.globalSkillsDir ??
|
|
5052
|
+
const targetDir = createPayload.scope === "global" ? path10.join(
|
|
5053
|
+
ctx.globalSkillsDir ?? path10.join(wstackGlobalRoot(), "skills"),
|
|
4750
5054
|
createPayload.name.trim()
|
|
4751
|
-
) :
|
|
4752
|
-
ctx.projectSkillsDir ??
|
|
5055
|
+
) : path10.join(
|
|
5056
|
+
ctx.projectSkillsDir ?? path10.join(ctx.projectRoot, ".wrongstack", "skills"),
|
|
4753
5057
|
createPayload.name.trim()
|
|
4754
5058
|
);
|
|
4755
5059
|
try {
|
|
4756
|
-
await
|
|
5060
|
+
await fs9.access(targetDir);
|
|
4757
5061
|
send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
|
|
4758
5062
|
return;
|
|
4759
5063
|
} catch {
|
|
4760
5064
|
}
|
|
4761
|
-
await
|
|
5065
|
+
await fs9.mkdir(targetDir, { recursive: true });
|
|
4762
5066
|
const lines = createPayload.description.trim().split("\n");
|
|
4763
5067
|
const firstLine = (lines[0] ?? "").trim();
|
|
4764
5068
|
const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
|
|
@@ -4806,13 +5110,13 @@ ${trigger}
|
|
|
4806
5110
|
"- `bug-hunter` \u2014 for systematic bug detection patterns",
|
|
4807
5111
|
"- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
|
|
4808
5112
|
].join("\n");
|
|
4809
|
-
await
|
|
5113
|
+
await atomicWrite5(path10.join(targetDir, "SKILL.md"), skillContent);
|
|
4810
5114
|
send(ws, {
|
|
4811
5115
|
type: "skills.created",
|
|
4812
5116
|
payload: {
|
|
4813
5117
|
success: true,
|
|
4814
5118
|
error: null,
|
|
4815
|
-
skill: { name: createPayload.name.trim(), path:
|
|
5119
|
+
skill: { name: createPayload.name.trim(), path: path10.join(targetDir, "SKILL.md"), scope: createPayload.scope }
|
|
4816
5120
|
}
|
|
4817
5121
|
});
|
|
4818
5122
|
} catch (err) {
|
|
@@ -4842,7 +5146,7 @@ async function handleSkillsEdit(ws, ctx, msg) {
|
|
|
4842
5146
|
send(ws, { type: "skills.edited", payload: { success: false, error: `${label} skills cannot be edited` } });
|
|
4843
5147
|
return;
|
|
4844
5148
|
}
|
|
4845
|
-
await
|
|
5149
|
+
await atomicWrite5(entry.path, editPayload.body);
|
|
4846
5150
|
send(ws, { type: "skills.edited", payload: { success: true, error: null } });
|
|
4847
5151
|
} catch (err) {
|
|
4848
5152
|
send(ws, { type: "skills.edited", payload: { success: false, error: errMessage(err) } });
|
|
@@ -4855,29 +5159,35 @@ async function handleSkillsExport(ws, ctx) {
|
|
|
4855
5159
|
}
|
|
4856
5160
|
try {
|
|
4857
5161
|
const entries = await ctx.skillLoader.listEntries();
|
|
4858
|
-
const
|
|
5162
|
+
const zipEntries = [];
|
|
4859
5163
|
for (const entry of entries) {
|
|
4860
5164
|
try {
|
|
4861
5165
|
const body = await ctx.skillLoader.readBody(entry.name);
|
|
4862
5166
|
const safeName = entry.name.replace(/\//g, "_");
|
|
4863
|
-
|
|
5167
|
+
zipEntries.push({ name: `${safeName}/SKILL.md`, data: body });
|
|
4864
5168
|
} catch {
|
|
4865
5169
|
}
|
|
4866
5170
|
}
|
|
4867
|
-
const zipBuffer =
|
|
5171
|
+
const zipBuffer = createZipBuffer(zipEntries);
|
|
4868
5172
|
const zipBase64 = zipBuffer.toString("base64");
|
|
4869
5173
|
send(ws, { type: "skills.exported", payload: { zipBase64, skillCount: entries.length, error: void 0 } });
|
|
4870
5174
|
} catch (err) {
|
|
4871
5175
|
send(ws, { type: "skills.exported", payload: { zipBase64: "", skillCount: 0, error: errMessage(err) } });
|
|
4872
5176
|
}
|
|
4873
5177
|
}
|
|
5178
|
+
|
|
5179
|
+
// src/server/specs-ws-handler.ts
|
|
5180
|
+
import {
|
|
5181
|
+
computeTaskProgress
|
|
5182
|
+
} from "@wrongstack/core";
|
|
5183
|
+
import { SpecStore as SpecStore2, TaskGraphStore as TaskGraphStore2 } from "@wrongstack/sdd";
|
|
4874
5184
|
var SpecsWebSocketHandler = class {
|
|
4875
5185
|
specStore;
|
|
4876
5186
|
graphStore;
|
|
4877
5187
|
clients = /* @__PURE__ */ new Set();
|
|
4878
5188
|
constructor(specsDir, taskGraphsDir) {
|
|
4879
|
-
this.specStore = new
|
|
4880
|
-
this.graphStore = new
|
|
5189
|
+
this.specStore = new SpecStore2({ baseDir: specsDir });
|
|
5190
|
+
this.graphStore = new TaskGraphStore2({ baseDir: taskGraphsDir });
|
|
4881
5191
|
}
|
|
4882
5192
|
addClient(ws) {
|
|
4883
5193
|
const client = { ws, id: crypto.randomUUID() };
|
|
@@ -5081,6 +5391,13 @@ function estimateContextBreakdown(input) {
|
|
|
5081
5391
|
messages: { total: msgTokens, count: input.messages.length, breakdown: messageBreakdown }
|
|
5082
5392
|
};
|
|
5083
5393
|
}
|
|
5394
|
+
|
|
5395
|
+
// src/server/worktree-ws-handler.ts
|
|
5396
|
+
import { join as join7, resolve as resolve6, sep as sep3 } from "node:path";
|
|
5397
|
+
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
|
|
5398
|
+
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
5399
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
5400
|
+
var MAX_ACTIVITY = 6;
|
|
5084
5401
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
|
|
5085
5402
|
var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
|
|
5086
5403
|
var WorktreeWebSocketHandler = class {
|
|
@@ -5137,13 +5454,13 @@ var WorktreeWebSocketHandler = class {
|
|
|
5137
5454
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
5138
5455
|
/** Absolute managed-worktrees root for this project. */
|
|
5139
5456
|
worktreesRoot() {
|
|
5140
|
-
return
|
|
5457
|
+
return resolve6(join7(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
5141
5458
|
}
|
|
5142
5459
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
5143
5460
|
underRoot(dir) {
|
|
5144
|
-
const abs =
|
|
5461
|
+
const abs = resolve6(dir);
|
|
5145
5462
|
const root = this.worktreesRoot();
|
|
5146
|
-
return abs !== root && abs.startsWith(root +
|
|
5463
|
+
return abs !== root && abs.startsWith(root + sep3);
|
|
5147
5464
|
}
|
|
5148
5465
|
/** Branches of worktrees a live in-session run currently owns. */
|
|
5149
5466
|
liveActiveBranches() {
|
|
@@ -5164,7 +5481,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5164
5481
|
return;
|
|
5165
5482
|
}
|
|
5166
5483
|
try {
|
|
5167
|
-
const wt = new
|
|
5484
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5168
5485
|
const { worktrees, branches } = await wt.listManaged();
|
|
5169
5486
|
const live = this.liveActiveBranches();
|
|
5170
5487
|
const orphans = [];
|
|
@@ -5188,7 +5505,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5188
5505
|
}
|
|
5189
5506
|
});
|
|
5190
5507
|
} catch (err) {
|
|
5191
|
-
this.logger.debug?.(`worktree orphan scan failed: ${
|
|
5508
|
+
this.logger.debug?.(`worktree orphan scan failed: ${toErrorMessage2(err)}`);
|
|
5192
5509
|
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
5193
5510
|
}
|
|
5194
5511
|
}
|
|
@@ -5212,7 +5529,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5212
5529
|
});
|
|
5213
5530
|
return;
|
|
5214
5531
|
}
|
|
5215
|
-
const res = await
|
|
5532
|
+
const res = await cleanupStaleSddWorktrees2({
|
|
5216
5533
|
projectRoot: this.management.projectRoot,
|
|
5217
5534
|
boardsDir: this.management.boardsDir
|
|
5218
5535
|
});
|
|
@@ -5251,7 +5568,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5251
5568
|
}
|
|
5252
5569
|
let removed = false;
|
|
5253
5570
|
if (dir) {
|
|
5254
|
-
const wt = new
|
|
5571
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5255
5572
|
({ removed } = await wt.removeOne(dir, branch));
|
|
5256
5573
|
}
|
|
5257
5574
|
for (const [id, h] of [...this.handles]) {
|
|
@@ -5275,7 +5592,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5275
5592
|
this.broadcast({ type: "worktree.merge_result", payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" } });
|
|
5276
5593
|
return;
|
|
5277
5594
|
}
|
|
5278
|
-
const wt = new
|
|
5595
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5279
5596
|
const res = await wt.mergeBranch(branch);
|
|
5280
5597
|
this.broadcast({
|
|
5281
5598
|
type: "worktree.merge_result",
|
|
@@ -5290,8 +5607,8 @@ var WorktreeWebSocketHandler = class {
|
|
|
5290
5607
|
return;
|
|
5291
5608
|
}
|
|
5292
5609
|
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
5293
|
-
const wt = new
|
|
5294
|
-
const summary = await wt.diffSummary(
|
|
5610
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5611
|
+
const summary = await wt.diffSummary(resolve6(dir), base);
|
|
5295
5612
|
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
5296
5613
|
}
|
|
5297
5614
|
// ── internals ───────────────────────────────────────────────────────────
|
|
@@ -5363,7 +5680,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5363
5680
|
activity(id, kind, text) {
|
|
5364
5681
|
const cur = this.handles.get(id);
|
|
5365
5682
|
if (cur) {
|
|
5366
|
-
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-
|
|
5683
|
+
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-MAX_ACTIVITY);
|
|
5367
5684
|
this.handles.set(id, { ...cur, recentActivity });
|
|
5368
5685
|
}
|
|
5369
5686
|
this.broadcast({ type: "worktree.event", payload: { kind, handleId: id, text, at: Date.now() } });
|
|
@@ -5395,7 +5712,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5395
5712
|
try {
|
|
5396
5713
|
if (ws.readyState === 1) ws.send(data);
|
|
5397
5714
|
} catch (err) {
|
|
5398
|
-
this.logger.debug?.(`worktree broadcast failed: ${
|
|
5715
|
+
this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage2(err)}`);
|
|
5399
5716
|
}
|
|
5400
5717
|
}
|
|
5401
5718
|
}
|
|
@@ -5407,6 +5724,12 @@ var WorktreeWebSocketHandler = class {
|
|
|
5407
5724
|
}
|
|
5408
5725
|
};
|
|
5409
5726
|
|
|
5727
|
+
// src/server/server-runtime.ts
|
|
5728
|
+
import * as path12 from "node:path";
|
|
5729
|
+
import { createRequire } from "node:module";
|
|
5730
|
+
import { fileURLToPath } from "node:url";
|
|
5731
|
+
import { WebSocketServer } from "ws";
|
|
5732
|
+
|
|
5410
5733
|
// src/server/lifecycle.ts
|
|
5411
5734
|
function createShutdown(res) {
|
|
5412
5735
|
const log = res.log ?? ((m) => console.log(m));
|
|
@@ -5442,9 +5765,14 @@ function registerShutdownHandlers(res) {
|
|
|
5442
5765
|
process.off("SIGTERM", shutdown);
|
|
5443
5766
|
};
|
|
5444
5767
|
}
|
|
5768
|
+
|
|
5769
|
+
// src/server/setup-events.ts
|
|
5770
|
+
import * as fs10 from "node:fs/promises";
|
|
5771
|
+
import { watch as fsWatch } from "node:fs";
|
|
5772
|
+
import * as path11 from "node:path";
|
|
5445
5773
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
5446
5774
|
const raw = String(filename);
|
|
5447
|
-
const relative4 =
|
|
5775
|
+
const relative4 = path11.isAbsolute(raw) ? path11.relative(projectsDir, raw) : raw;
|
|
5448
5776
|
const parts = relative4.split(/[\\/]+/).filter(Boolean);
|
|
5449
5777
|
if (parts.length < 2) return null;
|
|
5450
5778
|
if (parts[parts.length - 1] !== "status.json") return null;
|
|
@@ -5457,6 +5785,10 @@ function shouldLogWatcherStats() {
|
|
|
5457
5785
|
function setupEvents(deps2) {
|
|
5458
5786
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
5459
5787
|
const disposers = [];
|
|
5788
|
+
let disposed = false;
|
|
5789
|
+
const on = (event, listener) => {
|
|
5790
|
+
disposers.push(events.on(event, listener));
|
|
5791
|
+
};
|
|
5460
5792
|
const currentSessionId = () => context.session?.id ?? "";
|
|
5461
5793
|
const sessionPayload2 = (payload) => {
|
|
5462
5794
|
const provided = payload["sessionId"];
|
|
@@ -5472,20 +5804,20 @@ function setupEvents(deps2) {
|
|
|
5472
5804
|
sessionBridge?.append(event).catch(() => {
|
|
5473
5805
|
});
|
|
5474
5806
|
};
|
|
5475
|
-
|
|
5807
|
+
on("iteration.started", (e) => {
|
|
5476
5808
|
const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
|
|
5477
5809
|
broadcast2(clients, {
|
|
5478
5810
|
type: "iteration.started",
|
|
5479
5811
|
payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
|
|
5480
5812
|
});
|
|
5481
5813
|
});
|
|
5482
|
-
|
|
5814
|
+
on("iteration.completed", (e) => {
|
|
5483
5815
|
broadcast2(clients, {
|
|
5484
5816
|
type: "iteration.completed",
|
|
5485
5817
|
payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
|
|
5486
5818
|
});
|
|
5487
5819
|
});
|
|
5488
|
-
|
|
5820
|
+
on("iteration.limit_reached", (e) => {
|
|
5489
5821
|
broadcast2(clients, {
|
|
5490
5822
|
type: "iteration.limit_reached",
|
|
5491
5823
|
payload: sessionPayload2({
|
|
@@ -5495,19 +5827,19 @@ function setupEvents(deps2) {
|
|
|
5495
5827
|
})
|
|
5496
5828
|
});
|
|
5497
5829
|
});
|
|
5498
|
-
|
|
5830
|
+
on("provider.text_delta", (e) => {
|
|
5499
5831
|
broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
|
|
5500
5832
|
});
|
|
5501
|
-
|
|
5833
|
+
on("provider.thinking_delta", (e) => {
|
|
5502
5834
|
broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
|
|
5503
5835
|
});
|
|
5504
|
-
|
|
5836
|
+
on("provider.stream_error", (e) => {
|
|
5505
5837
|
broadcast2(clients, {
|
|
5506
5838
|
type: "provider.stream_error",
|
|
5507
5839
|
payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
|
|
5508
5840
|
});
|
|
5509
5841
|
});
|
|
5510
|
-
|
|
5842
|
+
on("tool.started", (e) => {
|
|
5511
5843
|
broadcast2(clients, {
|
|
5512
5844
|
type: "tool.started",
|
|
5513
5845
|
payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
|
|
@@ -5520,7 +5852,7 @@ function setupEvents(deps2) {
|
|
|
5520
5852
|
input: e.input
|
|
5521
5853
|
});
|
|
5522
5854
|
});
|
|
5523
|
-
|
|
5855
|
+
on("tool.progress", (e) => {
|
|
5524
5856
|
broadcast2(clients, {
|
|
5525
5857
|
type: "tool.progress",
|
|
5526
5858
|
// Nested `event` shape — the client handler reads `payload.event?.text`
|
|
@@ -5541,7 +5873,7 @@ function setupEvents(deps2) {
|
|
|
5541
5873
|
}
|
|
5542
5874
|
});
|
|
5543
5875
|
});
|
|
5544
|
-
|
|
5876
|
+
on("tool.executed", (e) => {
|
|
5545
5877
|
broadcast2(clients, {
|
|
5546
5878
|
type: "tool.executed",
|
|
5547
5879
|
payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
|
|
@@ -5581,7 +5913,7 @@ function setupEvents(deps2) {
|
|
|
5581
5913
|
try {
|
|
5582
5914
|
const taskPath = context.meta["task.path"];
|
|
5583
5915
|
if (typeof taskPath === "string" && taskPath) {
|
|
5584
|
-
const { loadTasks } = await import(
|
|
5916
|
+
const { loadTasks } = await import("@wrongstack/core");
|
|
5585
5917
|
const file = await loadTasks(taskPath);
|
|
5586
5918
|
broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
|
|
5587
5919
|
}
|
|
@@ -5590,7 +5922,7 @@ function setupEvents(deps2) {
|
|
|
5590
5922
|
try {
|
|
5591
5923
|
const planPath = context.meta["plan.path"];
|
|
5592
5924
|
if (typeof planPath === "string" && planPath) {
|
|
5593
|
-
const { loadPlan } = await import(
|
|
5925
|
+
const { loadPlan } = await import("@wrongstack/core");
|
|
5594
5926
|
const plan = await loadPlan(planPath);
|
|
5595
5927
|
broadcast2(clients, { type: "plan.updated", payload: sessionPayload2({ sessionId: e.sessionId, plan: plan ?? { version: 1, sessionId: e.sessionId ?? context.session?.id ?? "", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }) });
|
|
5596
5928
|
}
|
|
@@ -5599,7 +5931,7 @@ function setupEvents(deps2) {
|
|
|
5599
5931
|
})();
|
|
5600
5932
|
}
|
|
5601
5933
|
});
|
|
5602
|
-
|
|
5934
|
+
on("tool.loop_detected", (e) => {
|
|
5603
5935
|
broadcast2(clients, {
|
|
5604
5936
|
type: "tool.loop_detected",
|
|
5605
5937
|
payload: sessionPayload2({
|
|
@@ -5611,19 +5943,19 @@ function setupEvents(deps2) {
|
|
|
5611
5943
|
})
|
|
5612
5944
|
});
|
|
5613
5945
|
});
|
|
5614
|
-
|
|
5946
|
+
on("trust.persisted", (e) => {
|
|
5615
5947
|
broadcast2(clients, {
|
|
5616
5948
|
type: "trust.persisted",
|
|
5617
5949
|
payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
|
|
5618
5950
|
});
|
|
5619
5951
|
});
|
|
5620
|
-
|
|
5952
|
+
on("delegate.started", (e) => {
|
|
5621
5953
|
broadcast2(clients, {
|
|
5622
5954
|
type: "delegate.started",
|
|
5623
5955
|
payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
|
|
5624
5956
|
});
|
|
5625
5957
|
});
|
|
5626
|
-
|
|
5958
|
+
on("delegate.completed", (e) => {
|
|
5627
5959
|
broadcast2(clients, {
|
|
5628
5960
|
type: "delegate.completed",
|
|
5629
5961
|
payload: sessionPayload2({
|
|
@@ -5641,7 +5973,7 @@ function setupEvents(deps2) {
|
|
|
5641
5973
|
})
|
|
5642
5974
|
});
|
|
5643
5975
|
});
|
|
5644
|
-
|
|
5976
|
+
on("provider.response", (e) => {
|
|
5645
5977
|
broadcast2(clients, {
|
|
5646
5978
|
type: "provider.response",
|
|
5647
5979
|
payload: sessionPayload2({
|
|
@@ -5653,7 +5985,7 @@ function setupEvents(deps2) {
|
|
|
5653
5985
|
})
|
|
5654
5986
|
});
|
|
5655
5987
|
});
|
|
5656
|
-
|
|
5988
|
+
on("ctx.pct", (e) => {
|
|
5657
5989
|
broadcast2(clients, {
|
|
5658
5990
|
type: "ctx.pct",
|
|
5659
5991
|
payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
|
|
@@ -5670,28 +6002,28 @@ function setupEvents(deps2) {
|
|
|
5670
6002
|
})
|
|
5671
6003
|
});
|
|
5672
6004
|
});
|
|
5673
|
-
|
|
6005
|
+
on("ctx.max_context", (e) => {
|
|
5674
6006
|
broadcast2(clients, {
|
|
5675
6007
|
type: "ctx.max_context",
|
|
5676
6008
|
payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
|
|
5677
6009
|
});
|
|
5678
6010
|
});
|
|
5679
|
-
|
|
6011
|
+
on("token.threshold", (e) => {
|
|
5680
6012
|
broadcast2(clients, {
|
|
5681
6013
|
type: "token.threshold",
|
|
5682
6014
|
payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
|
|
5683
6015
|
});
|
|
5684
6016
|
});
|
|
5685
|
-
|
|
6017
|
+
on("token.cost_estimate_unavailable", (e) => {
|
|
5686
6018
|
broadcast2(clients, {
|
|
5687
6019
|
type: "token.cost_estimate_unavailable",
|
|
5688
6020
|
payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
|
|
5689
6021
|
});
|
|
5690
6022
|
});
|
|
5691
|
-
|
|
6023
|
+
on("context.repaired", (e) => {
|
|
5692
6024
|
broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
|
|
5693
6025
|
});
|
|
5694
|
-
|
|
6026
|
+
on("tool.confirm_needed", (e) => {
|
|
5695
6027
|
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
5696
6028
|
const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
|
|
5697
6029
|
pendingConfirms.set(id, {
|
|
@@ -5702,7 +6034,7 @@ function setupEvents(deps2) {
|
|
|
5702
6034
|
});
|
|
5703
6035
|
broadcast2(clients, { type: "tool.confirm_needed", payload });
|
|
5704
6036
|
});
|
|
5705
|
-
|
|
6037
|
+
on("error", (e) => {
|
|
5706
6038
|
broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
|
|
5707
6039
|
appendForCurrentSession(e.sessionId, {
|
|
5708
6040
|
type: "error",
|
|
@@ -5711,13 +6043,13 @@ function setupEvents(deps2) {
|
|
|
5711
6043
|
phase: e.phase
|
|
5712
6044
|
});
|
|
5713
6045
|
});
|
|
5714
|
-
|
|
6046
|
+
on("session.damaged", (e) => {
|
|
5715
6047
|
broadcast2(clients, {
|
|
5716
6048
|
type: "session.damaged",
|
|
5717
6049
|
payload: { sessionId: e.sessionId, detail: e.detail }
|
|
5718
6050
|
});
|
|
5719
6051
|
});
|
|
5720
|
-
|
|
6052
|
+
on("session.rewound", (e) => {
|
|
5721
6053
|
broadcast2(clients, {
|
|
5722
6054
|
type: "session.rewound",
|
|
5723
6055
|
payload: sessionPayload2({
|
|
@@ -5728,7 +6060,7 @@ function setupEvents(deps2) {
|
|
|
5728
6060
|
})
|
|
5729
6061
|
});
|
|
5730
6062
|
});
|
|
5731
|
-
|
|
6063
|
+
on("checkpoint.written", (e) => {
|
|
5732
6064
|
broadcast2(clients, {
|
|
5733
6065
|
type: "checkpoint.written",
|
|
5734
6066
|
payload: sessionPayload2({
|
|
@@ -5740,19 +6072,19 @@ function setupEvents(deps2) {
|
|
|
5740
6072
|
})
|
|
5741
6073
|
});
|
|
5742
6074
|
});
|
|
5743
|
-
|
|
6075
|
+
on("in_flight.started", (e) => {
|
|
5744
6076
|
broadcast2(clients, {
|
|
5745
6077
|
type: "in_flight.started",
|
|
5746
6078
|
payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
|
|
5747
6079
|
});
|
|
5748
6080
|
});
|
|
5749
|
-
|
|
6081
|
+
on("in_flight.ended", (e) => {
|
|
5750
6082
|
broadcast2(clients, {
|
|
5751
6083
|
type: "in_flight.ended",
|
|
5752
6084
|
payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
|
|
5753
6085
|
});
|
|
5754
6086
|
});
|
|
5755
|
-
|
|
6087
|
+
on("provider.retry", (e) => {
|
|
5756
6088
|
broadcast2(clients, {
|
|
5757
6089
|
type: "provider.retry",
|
|
5758
6090
|
payload: sessionPayload2({
|
|
@@ -5774,7 +6106,7 @@ function setupEvents(deps2) {
|
|
|
5774
6106
|
description: e.description
|
|
5775
6107
|
});
|
|
5776
6108
|
});
|
|
5777
|
-
|
|
6109
|
+
on("provider.error", (e) => {
|
|
5778
6110
|
broadcast2(clients, {
|
|
5779
6111
|
type: "provider.error",
|
|
5780
6112
|
payload: sessionPayload2({
|
|
@@ -5794,7 +6126,7 @@ function setupEvents(deps2) {
|
|
|
5794
6126
|
retryable: e.retryable
|
|
5795
6127
|
});
|
|
5796
6128
|
});
|
|
5797
|
-
|
|
6129
|
+
on("provider.fallback", (e) => {
|
|
5798
6130
|
broadcast2(clients, {
|
|
5799
6131
|
type: "provider.fallback",
|
|
5800
6132
|
payload: sessionPayload2({
|
|
@@ -5806,7 +6138,7 @@ function setupEvents(deps2) {
|
|
|
5806
6138
|
})
|
|
5807
6139
|
});
|
|
5808
6140
|
});
|
|
5809
|
-
|
|
6141
|
+
on("compaction.fired", (e) => {
|
|
5810
6142
|
broadcast2(clients, {
|
|
5811
6143
|
type: "context.compacted",
|
|
5812
6144
|
payload: sessionPayload2({
|
|
@@ -5818,7 +6150,7 @@ function setupEvents(deps2) {
|
|
|
5818
6150
|
})
|
|
5819
6151
|
});
|
|
5820
6152
|
});
|
|
5821
|
-
|
|
6153
|
+
on("compaction.failed", (e) => {
|
|
5822
6154
|
broadcast2(clients, {
|
|
5823
6155
|
type: "compaction.failed",
|
|
5824
6156
|
payload: sessionPayload2({
|
|
@@ -5833,25 +6165,25 @@ function setupEvents(deps2) {
|
|
|
5833
6165
|
})
|
|
5834
6166
|
});
|
|
5835
6167
|
});
|
|
5836
|
-
|
|
6168
|
+
on("mcp.server.connected", (e) => {
|
|
5837
6169
|
broadcast2(clients, {
|
|
5838
6170
|
type: "mcp.server.connected",
|
|
5839
6171
|
payload: { name: e.name, toolCount: e.toolCount }
|
|
5840
6172
|
});
|
|
5841
6173
|
});
|
|
5842
|
-
|
|
6174
|
+
on("mcp.server.reconnected", (e) => {
|
|
5843
6175
|
broadcast2(clients, {
|
|
5844
6176
|
type: "mcp.server.reconnected",
|
|
5845
6177
|
payload: { name: e.name, toolCount: e.toolCount }
|
|
5846
6178
|
});
|
|
5847
6179
|
});
|
|
5848
|
-
|
|
6180
|
+
on("mcp.server.disconnected", (e) => {
|
|
5849
6181
|
broadcast2(clients, {
|
|
5850
6182
|
type: "mcp.server.disconnected",
|
|
5851
6183
|
payload: { name: e.name, reason: e.reason }
|
|
5852
6184
|
});
|
|
5853
6185
|
});
|
|
5854
|
-
|
|
6186
|
+
on("coordinator.stats", (e) => {
|
|
5855
6187
|
broadcast2(clients, {
|
|
5856
6188
|
type: "coordinator.stats",
|
|
5857
6189
|
payload: sessionPayload2({
|
|
@@ -5879,15 +6211,16 @@ function setupEvents(deps2) {
|
|
|
5879
6211
|
broadcast2(clients, { type: "mailbox.agent_registered", payload });
|
|
5880
6212
|
});
|
|
5881
6213
|
const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
6214
|
+
on("subagent.spawned", (e) => forwardSubagent("spawned", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
|
|
6215
|
+
on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
|
|
6216
|
+
on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
|
|
6217
|
+
on("subagent.iteration_summary", (e) => forwardSubagent("iteration_summary", { sessionId: e.sessionId, subagentId: e.subagentId, iteration: e.iteration, toolCalls: e.toolCalls, costUsd: e.costUsd, currentTool: e.currentTool, partialText: e.partialText }));
|
|
6218
|
+
on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
|
|
6219
|
+
on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
|
|
6220
|
+
on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
|
|
6221
|
+
on("subagent.task_completed", (e) => forwardSubagent("task_completed", { sessionId: e.sessionId, subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, finalText: e.finalText, failureReason: e.error?.kind, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
|
|
6222
|
+
on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
|
|
6223
|
+
on("agent.timeline.message", (e) => {
|
|
5891
6224
|
broadcast2(clients, {
|
|
5892
6225
|
type: "agent.timeline.message",
|
|
5893
6226
|
payload: sessionPayload2({
|
|
@@ -5903,7 +6236,7 @@ function setupEvents(deps2) {
|
|
|
5903
6236
|
})
|
|
5904
6237
|
});
|
|
5905
6238
|
});
|
|
5906
|
-
|
|
6239
|
+
on("agent.status_changed", (e) => {
|
|
5907
6240
|
broadcast2(clients, {
|
|
5908
6241
|
type: "agent.status_changed",
|
|
5909
6242
|
payload: sessionPayload2({
|
|
@@ -5918,7 +6251,7 @@ function setupEvents(deps2) {
|
|
|
5918
6251
|
});
|
|
5919
6252
|
});
|
|
5920
6253
|
let leaderSpawned = false;
|
|
5921
|
-
|
|
6254
|
+
on("iteration.started", (e) => {
|
|
5922
6255
|
if (!leaderSpawned) {
|
|
5923
6256
|
leaderSpawned = true;
|
|
5924
6257
|
const provider = context.provider?.id ?? "unknown";
|
|
@@ -5932,7 +6265,7 @@ function setupEvents(deps2) {
|
|
|
5932
6265
|
});
|
|
5933
6266
|
}
|
|
5934
6267
|
});
|
|
5935
|
-
|
|
6268
|
+
on("tool.executed", (e) => {
|
|
5936
6269
|
forwardSubagent("tool_executed", {
|
|
5937
6270
|
sessionId: e.sessionId,
|
|
5938
6271
|
subagentId: "leader",
|
|
@@ -5941,7 +6274,7 @@ function setupEvents(deps2) {
|
|
|
5941
6274
|
ok: e.ok
|
|
5942
6275
|
});
|
|
5943
6276
|
});
|
|
5944
|
-
|
|
6277
|
+
on("provider.response", (e) => {
|
|
5945
6278
|
if (e.usage?.input != null) {
|
|
5946
6279
|
const maxCtx = context.provider.capabilities.maxContext;
|
|
5947
6280
|
const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
|
|
@@ -5958,7 +6291,7 @@ function setupEvents(deps2) {
|
|
|
5958
6291
|
});
|
|
5959
6292
|
}
|
|
5960
6293
|
});
|
|
5961
|
-
|
|
6294
|
+
on("iteration.completed", (e) => {
|
|
5962
6295
|
if (!leaderSpawned) {
|
|
5963
6296
|
leaderSpawned = true;
|
|
5964
6297
|
const provider = context.provider?.id ?? "unknown";
|
|
@@ -5984,14 +6317,20 @@ function setupEvents(deps2) {
|
|
|
5984
6317
|
payload: sessionPayload2({ event: eventName, ...payload })
|
|
5985
6318
|
});
|
|
5986
6319
|
});
|
|
5987
|
-
events.
|
|
6320
|
+
events.onPattern("memory.*", (eventName, payload) => {
|
|
6321
|
+
broadcast2(clients, {
|
|
6322
|
+
type: "memory.event",
|
|
6323
|
+
payload: sessionPayload2({ event: eventName, ...payload })
|
|
6324
|
+
});
|
|
6325
|
+
});
|
|
6326
|
+
on("client.status", async (e) => {
|
|
5988
6327
|
broadcast2(clients, { type: "client.status_update", payload: e });
|
|
5989
6328
|
if (wpaths?.projectStatus) {
|
|
5990
6329
|
try {
|
|
5991
6330
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
5992
|
-
const dir =
|
|
5993
|
-
await
|
|
5994
|
-
await
|
|
6331
|
+
const dir = path11.dirname(statusFile);
|
|
6332
|
+
await fs10.mkdir(dir, { recursive: true });
|
|
6333
|
+
await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
5995
6334
|
} catch (err) {
|
|
5996
6335
|
console.error(
|
|
5997
6336
|
JSON.stringify({
|
|
@@ -6005,7 +6344,7 @@ function setupEvents(deps2) {
|
|
|
6005
6344
|
}
|
|
6006
6345
|
});
|
|
6007
6346
|
if (wpaths?.projectStatus && wpaths.configDir) {
|
|
6008
|
-
const projectsDir =
|
|
6347
|
+
const projectsDir = path11.join(wpaths.configDir, "projects");
|
|
6009
6348
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
6010
6349
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
6011
6350
|
const DEBOUNCE_MS = 150;
|
|
@@ -6069,8 +6408,9 @@ function setupEvents(deps2) {
|
|
|
6069
6408
|
let watcher;
|
|
6070
6409
|
const startWatcher = async () => {
|
|
6071
6410
|
try {
|
|
6072
|
-
await
|
|
6073
|
-
|
|
6411
|
+
await fs10.mkdir(projectsDir, { recursive: true });
|
|
6412
|
+
if (disposed) return;
|
|
6413
|
+
watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
|
|
6074
6414
|
if (eventType !== "change" && eventType !== "rename") return;
|
|
6075
6415
|
if (filename == null) return;
|
|
6076
6416
|
const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
|
|
@@ -6079,8 +6419,8 @@ function setupEvents(deps2) {
|
|
|
6079
6419
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
6080
6420
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
6081
6421
|
try {
|
|
6082
|
-
const targetFile =
|
|
6083
|
-
const content = await
|
|
6422
|
+
const targetFile = path11.join(projectsDir, projectHash, "status.json");
|
|
6423
|
+
const content = await fs10.readFile(targetFile, "utf-8");
|
|
6084
6424
|
const statusData = JSON.parse(content);
|
|
6085
6425
|
scheduleBroadcast(projectHash, statusData);
|
|
6086
6426
|
} catch {
|
|
@@ -6100,7 +6440,7 @@ function setupEvents(deps2) {
|
|
|
6100
6440
|
);
|
|
6101
6441
|
}
|
|
6102
6442
|
};
|
|
6103
|
-
|
|
6443
|
+
on("client.status", (e) => {
|
|
6104
6444
|
if (e.projectHash) {
|
|
6105
6445
|
const hash = String(e.projectHash);
|
|
6106
6446
|
if (!knownProjectHashes.has(hash)) {
|
|
@@ -6132,15 +6472,17 @@ function setupEvents(deps2) {
|
|
|
6132
6472
|
}
|
|
6133
6473
|
});
|
|
6134
6474
|
}
|
|
6135
|
-
const globalRoot = globalConfigPath ?
|
|
6475
|
+
const globalRoot = globalConfigPath ? path11.dirname(globalConfigPath) : void 0;
|
|
6136
6476
|
if (globalRoot) {
|
|
6137
6477
|
const broadcastSessions = async () => {
|
|
6138
6478
|
try {
|
|
6139
|
-
const { SessionRegistry } = await import(
|
|
6479
|
+
const { SessionRegistry } = await import("@wrongstack/core");
|
|
6140
6480
|
const registry = new SessionRegistry(globalRoot);
|
|
6141
6481
|
const sessions = await registry.list();
|
|
6142
6482
|
const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
|
|
6143
|
-
const live = sessions.filter(
|
|
6483
|
+
const live = sessions.filter(
|
|
6484
|
+
(s) => s.status === "active" || s.status === "idle"
|
|
6485
|
+
).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
|
|
6144
6486
|
sessionId: s.sessionId,
|
|
6145
6487
|
projectName: s.projectName,
|
|
6146
6488
|
projectSlug: s.projectSlug,
|
|
@@ -6179,7 +6521,7 @@ function setupEvents(deps2) {
|
|
|
6179
6521
|
disposers.push(() => clearInterval(statusInterval));
|
|
6180
6522
|
let regDebounce;
|
|
6181
6523
|
try {
|
|
6182
|
-
const regWatcher =
|
|
6524
|
+
const regWatcher = fsWatch(globalRoot, { persistent: false }, (_event, filename) => {
|
|
6183
6525
|
const name2 = filename ? String(filename) : "";
|
|
6184
6526
|
if (!name2.startsWith("session-registry.json") || name2.endsWith(".lock")) return;
|
|
6185
6527
|
if (regDebounce) clearTimeout(regDebounce);
|
|
@@ -6194,6 +6536,8 @@ function setupEvents(deps2) {
|
|
|
6194
6536
|
void broadcastSessions();
|
|
6195
6537
|
}
|
|
6196
6538
|
return () => {
|
|
6539
|
+
if (disposed) return;
|
|
6540
|
+
disposed = true;
|
|
6197
6541
|
for (const dispose of disposers) {
|
|
6198
6542
|
try {
|
|
6199
6543
|
dispose();
|
|
@@ -6215,6 +6559,9 @@ function getCostRates(model) {
|
|
|
6215
6559
|
function computeUsageCost(usage, rates) {
|
|
6216
6560
|
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
6217
6561
|
}
|
|
6562
|
+
|
|
6563
|
+
// src/server/model-catalog.ts
|
|
6564
|
+
import { capabilitiesFor } from "@wrongstack/providers";
|
|
6218
6565
|
var SIBLING_CATALOG = {
|
|
6219
6566
|
"anthropic-oauth": "anthropic",
|
|
6220
6567
|
"openai-codex": "openai",
|
|
@@ -6321,6 +6668,9 @@ function readConfiguredBool(providerConfig, key) {
|
|
|
6321
6668
|
function positiveNumber(value) {
|
|
6322
6669
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
6323
6670
|
}
|
|
6671
|
+
|
|
6672
|
+
// src/server/server-runtime.ts
|
|
6673
|
+
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
6324
6674
|
async function resolvePorts(opts) {
|
|
6325
6675
|
const requestedWsPort = opts.wsPort ?? 3457;
|
|
6326
6676
|
const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
|
|
@@ -6381,7 +6731,7 @@ function createSessionStartPayload(g) {
|
|
|
6381
6731
|
inputCost,
|
|
6382
6732
|
outputCost,
|
|
6383
6733
|
cacheReadCost,
|
|
6384
|
-
projectName:
|
|
6734
|
+
projectName: path12.basename(projectRoot) || projectRoot,
|
|
6385
6735
|
projectRoot,
|
|
6386
6736
|
cwd: g.getWorkingDir(),
|
|
6387
6737
|
mode: g.getModeId(),
|
|
@@ -6414,18 +6764,31 @@ function createWsServers(ports, accessToken) {
|
|
|
6414
6764
|
allowedHostnames: publicHostnames,
|
|
6415
6765
|
allowBrowserUrlToken: Boolean(ports.publicWsUrl)
|
|
6416
6766
|
});
|
|
6417
|
-
const WS_MAX_PAYLOAD =
|
|
6767
|
+
const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
|
|
6418
6768
|
const wssPrimary = new WebSocketServer({
|
|
6419
6769
|
port: ports.wsPort,
|
|
6420
6770
|
host: ports.wsHost,
|
|
6421
6771
|
verifyClient: verifyClient2,
|
|
6422
|
-
maxPayload: WS_MAX_PAYLOAD
|
|
6772
|
+
maxPayload: WS_MAX_PAYLOAD,
|
|
6773
|
+
// Send a ping every 15s to keep idle connections alive. Without this,
|
|
6774
|
+
// network equipment (routers, proxies, NAT gateways) may drop idle TCP
|
|
6775
|
+
// connections, causing the browser to see a close event and show the
|
|
6776
|
+
// "reconnecting" banner. The browser responds with a pong automatically
|
|
6777
|
+
// (the WebSocket API handles this at the transport level).
|
|
6778
|
+
pingInterval: 15e3,
|
|
6779
|
+
// Wait 5s for a pong before considering the connection dead. A client
|
|
6780
|
+
// that hasn't responded to 3 consecutive pings in a row (45s total
|
|
6781
|
+
// quiet) is almost certainly unreachable — close the socket so the
|
|
6782
|
+
// client can reconnect cleanly rather than hanging indefinitely.
|
|
6783
|
+
pingTimeout: 5e3
|
|
6423
6784
|
});
|
|
6424
6785
|
const wssSecondary = ports.wsHost === "127.0.0.1" ? new WebSocketServer({
|
|
6425
6786
|
port: ports.wsPort,
|
|
6426
6787
|
host: "::1",
|
|
6427
6788
|
verifyClient: verifyClient2,
|
|
6428
|
-
maxPayload: WS_MAX_PAYLOAD
|
|
6789
|
+
maxPayload: WS_MAX_PAYLOAD,
|
|
6790
|
+
pingInterval: 15e3,
|
|
6791
|
+
pingTimeout: 5e3
|
|
6429
6792
|
}) : null;
|
|
6430
6793
|
const clients = /* @__PURE__ */ new Map();
|
|
6431
6794
|
console.log(
|
|
@@ -6447,7 +6810,7 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
|
|
|
6447
6810
|
};
|
|
6448
6811
|
wssPrimary.on("listening", () => arm(`${wsHost}:${wsPort}`));
|
|
6449
6812
|
wssPrimary.on("error", (err) => {
|
|
6450
|
-
console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message:
|
|
6813
|
+
console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage3(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
6451
6814
|
});
|
|
6452
6815
|
if (wssSecondary) {
|
|
6453
6816
|
wssSecondary.on("listening", () => arm(`::1:${wsPort}`));
|
|
@@ -6466,13 +6829,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
|
|
|
6466
6829
|
};
|
|
6467
6830
|
}
|
|
6468
6831
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
6469
|
-
if (explicitDistDir) return
|
|
6832
|
+
if (explicitDistDir) return path12.resolve(explicitDistDir);
|
|
6470
6833
|
try {
|
|
6471
6834
|
const requireFromHere2 = createRequire(fromUrl);
|
|
6472
6835
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui/server");
|
|
6473
|
-
return
|
|
6836
|
+
return path12.resolve(path12.dirname(serverEntry), "..");
|
|
6474
6837
|
} catch {
|
|
6475
|
-
return
|
|
6838
|
+
return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
6476
6839
|
}
|
|
6477
6840
|
}
|
|
6478
6841
|
function startHttpServer(opts) {
|
|
@@ -6487,13 +6850,13 @@ function startHttpServer(opts) {
|
|
|
6487
6850
|
watcherMetrics: opts.watcherMetrics,
|
|
6488
6851
|
onFleetPing: opts.onFleetPing
|
|
6489
6852
|
});
|
|
6490
|
-
const registryBaseDir =
|
|
6853
|
+
const registryBaseDir = path12.dirname(opts.globalConfigPath);
|
|
6491
6854
|
httpServer.listen(opts.httpPort, opts.wsHost, () => {
|
|
6492
6855
|
const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
|
|
6493
6856
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
6494
6857
|
if (opts.openBrowser) openBrowser(openUrl);
|
|
6495
6858
|
void registerInstance(
|
|
6496
|
-
{ pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName:
|
|
6859
|
+
{ pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path12.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
6497
6860
|
registryBaseDir
|
|
6498
6861
|
).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
|
|
6499
6862
|
});
|
|
@@ -6507,6 +6870,65 @@ function registerShutdown(deps2) {
|
|
|
6507
6870
|
onShutdown: deps2.onShutdown
|
|
6508
6871
|
});
|
|
6509
6872
|
}
|
|
6873
|
+
|
|
6874
|
+
// src/server/pre-context-services.ts
|
|
6875
|
+
import * as path15 from "node:path";
|
|
6876
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
6877
|
+
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
6878
|
+
import {
|
|
6879
|
+
AnnotationsStore,
|
|
6880
|
+
DEFAULT_SESSION_PRUNE_DAYS,
|
|
6881
|
+
DefaultModelsRegistry,
|
|
6882
|
+
DefaultModeStore,
|
|
6883
|
+
DefaultPromptLoader,
|
|
6884
|
+
DefaultSessionReader,
|
|
6885
|
+
DefaultSessionStore,
|
|
6886
|
+
DefaultSkillLoader,
|
|
6887
|
+
DefaultSystemPromptBuilder,
|
|
6888
|
+
EventBus,
|
|
6889
|
+
GlobalMailbox,
|
|
6890
|
+
PromptUsageStore,
|
|
6891
|
+
ProviderRegistry,
|
|
6892
|
+
SkillInstaller,
|
|
6893
|
+
TOKENS,
|
|
6894
|
+
ToolRegistry,
|
|
6895
|
+
Context,
|
|
6896
|
+
applyToolDescriptionModes,
|
|
6897
|
+
applyToolResultRenderModes,
|
|
6898
|
+
configureChildEnvGitIdentity,
|
|
6899
|
+
getSessionRegistry as getSessionRegistry2,
|
|
6900
|
+
resolveContextWindowPolicy,
|
|
6901
|
+
makeMailboxTool,
|
|
6902
|
+
makeMailInboxTool,
|
|
6903
|
+
makeFleetStatusTool,
|
|
6904
|
+
makeMailSendTool
|
|
6905
|
+
} from "@wrongstack/core";
|
|
6906
|
+
import {
|
|
6907
|
+
createSuperMemoryTools
|
|
6908
|
+
} from "@wrongstack/super-memory";
|
|
6909
|
+
import {
|
|
6910
|
+
MCPAuthorizationManager,
|
|
6911
|
+
MCPRegistry,
|
|
6912
|
+
MCPVaultTokenStore,
|
|
6913
|
+
createVaultBackedMcpAuthorizationProviderFactory
|
|
6914
|
+
} from "@wrongstack/mcp";
|
|
6915
|
+
import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
|
|
6916
|
+
import { createDefaultContainer } from "@wrongstack/runtime";
|
|
6917
|
+
import {
|
|
6918
|
+
builtinToolsPack,
|
|
6919
|
+
configureDangerBypass,
|
|
6920
|
+
configureExecPolicy,
|
|
6921
|
+
forgetTool,
|
|
6922
|
+
relatedMemoryTool,
|
|
6923
|
+
rememberTool,
|
|
6924
|
+
searchMemoryTool
|
|
6925
|
+
} from "@wrongstack/tools";
|
|
6926
|
+
import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
6927
|
+
|
|
6928
|
+
// src/server/setup-screen.ts
|
|
6929
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
6930
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
6931
|
+
import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
6510
6932
|
var UNCONFIGURED_CAPABILITIES = {
|
|
6511
6933
|
tools: false,
|
|
6512
6934
|
parallelTools: false,
|
|
@@ -6546,7 +6968,7 @@ function logCreateFailure(event, err) {
|
|
|
6546
6968
|
JSON.stringify({
|
|
6547
6969
|
level: "error",
|
|
6548
6970
|
event,
|
|
6549
|
-
message:
|
|
6971
|
+
message: toErrorMessage4(err),
|
|
6550
6972
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6551
6973
|
})
|
|
6552
6974
|
);
|
|
@@ -6571,7 +6993,7 @@ function resolveSetupProvider(opts) {
|
|
|
6571
6993
|
const savedProviders = config.providers ?? {};
|
|
6572
6994
|
const firstKey = Object.keys(savedProviders)[0];
|
|
6573
6995
|
if (firstKey) {
|
|
6574
|
-
const firstProvider =
|
|
6996
|
+
const firstProvider = expectDefined2(savedProviders[firstKey]);
|
|
6575
6997
|
try {
|
|
6576
6998
|
const provider2 = makeProviderFromConfig(firstKey, {
|
|
6577
6999
|
...firstProvider,
|
|
@@ -6639,6 +7061,20 @@ function seedContextMeta(config, context) {
|
|
|
6639
7061
|
meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
|
|
6640
7062
|
meta["cacheTtl"] = mr.cache?.ttl ?? "default";
|
|
6641
7063
|
}
|
|
7064
|
+
meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
|
|
7065
|
+
meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
|
|
7066
|
+
meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
|
|
7067
|
+
meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
|
|
7068
|
+
meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
|
|
7069
|
+
meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
|
|
7070
|
+
meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
|
|
7071
|
+
{
|
|
7072
|
+
const featuresAllow = config.features?.allowOutsideProjectRoot;
|
|
7073
|
+
const toolsRestrict = config.tools?.restrictToProjectRoot;
|
|
7074
|
+
const allow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
|
|
7075
|
+
meta["fsAccess"] = allow ? "unrestricted" : "project";
|
|
7076
|
+
}
|
|
7077
|
+
meta["debugStream"] = config.debugStream === true;
|
|
6642
7078
|
const hqConfig = config.hq;
|
|
6643
7079
|
meta["hqEnabled"] = hqConfig?.enabled === true;
|
|
6644
7080
|
meta["hqUrl"] = hqConfig?.url ?? "";
|
|
@@ -6651,6 +7087,11 @@ function seedContextMeta(config, context) {
|
|
|
6651
7087
|
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
6652
7088
|
meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
6653
7089
|
}
|
|
7090
|
+
|
|
7091
|
+
// src/server/model-auto-discovery.ts
|
|
7092
|
+
import * as fs11 from "node:fs/promises";
|
|
7093
|
+
import * as path13 from "node:path";
|
|
7094
|
+
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
6654
7095
|
function isOverlayRegistry(value) {
|
|
6655
7096
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
6656
7097
|
}
|
|
@@ -6675,7 +7116,7 @@ function eligibleProviders(config) {
|
|
|
6675
7116
|
}
|
|
6676
7117
|
async function readCache(file) {
|
|
6677
7118
|
try {
|
|
6678
|
-
return JSON.parse(await
|
|
7119
|
+
return JSON.parse(await fs11.readFile(file, "utf8"));
|
|
6679
7120
|
} catch {
|
|
6680
7121
|
return {};
|
|
6681
7122
|
}
|
|
@@ -6685,7 +7126,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
6685
7126
|
if (!isOverlayRegistry(registry)) return;
|
|
6686
7127
|
const targets = eligibleProviders(opts.config);
|
|
6687
7128
|
if (targets.length === 0) return;
|
|
6688
|
-
const cacheFile =
|
|
7129
|
+
const cacheFile = path13.join(opts.cacheDir, "discovered-models-cache.json");
|
|
6689
7130
|
const cache = await readCache(cacheFile);
|
|
6690
7131
|
let cacheDirty = false;
|
|
6691
7132
|
await Promise.all(
|
|
@@ -6722,18 +7163,196 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
6722
7163
|
);
|
|
6723
7164
|
if (cacheDirty) {
|
|
6724
7165
|
try {
|
|
6725
|
-
await
|
|
6726
|
-
await
|
|
7166
|
+
await fs11.mkdir(path13.dirname(cacheFile), { recursive: true });
|
|
7167
|
+
await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
|
|
6727
7168
|
} catch {
|
|
6728
7169
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
6729
7170
|
}
|
|
6730
7171
|
}
|
|
6731
7172
|
}
|
|
6732
7173
|
|
|
7174
|
+
// src/server/standalone-session-identity.ts
|
|
7175
|
+
import * as path14 from "node:path";
|
|
7176
|
+
import {
|
|
7177
|
+
AgentStatusTracker,
|
|
7178
|
+
FleetNotifier,
|
|
7179
|
+
getSessionRegistry,
|
|
7180
|
+
RecoveryLock
|
|
7181
|
+
} from "@wrongstack/core";
|
|
7182
|
+
import { WebSocket as WebSocket2 } from "ws";
|
|
7183
|
+
async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
7184
|
+
const { paths, events, logger } = opts;
|
|
7185
|
+
const registry = opts.sessionRegistry ?? getSessionRegistry(paths.globalRoot);
|
|
7186
|
+
const fleetNotifier = new FleetNotifier({
|
|
7187
|
+
baseDir: paths.globalRoot,
|
|
7188
|
+
projectRoot: paths.projectRoot,
|
|
7189
|
+
selfPid: process.pid
|
|
7190
|
+
});
|
|
7191
|
+
let activeSessionId = opts.initialSessionId;
|
|
7192
|
+
let stopped = false;
|
|
7193
|
+
let transition = Promise.resolve();
|
|
7194
|
+
const statusTracker = new AgentStatusTracker({
|
|
7195
|
+
events,
|
|
7196
|
+
registry,
|
|
7197
|
+
sessionId: () => activeSessionId,
|
|
7198
|
+
onUpdate: () => fleetNotifier.notify()
|
|
7199
|
+
});
|
|
7200
|
+
const register = async (sessionId) => {
|
|
7201
|
+
try {
|
|
7202
|
+
await registry.register({
|
|
7203
|
+
sessionId,
|
|
7204
|
+
projectSlug: paths.projectSlug,
|
|
7205
|
+
projectRoot: paths.projectRoot,
|
|
7206
|
+
projectName: path14.basename(paths.projectRoot),
|
|
7207
|
+
workingDir: opts.workingDir,
|
|
7208
|
+
clientType: "webui",
|
|
7209
|
+
pid: process.pid,
|
|
7210
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7211
|
+
agents: statusTracker.getAgents()
|
|
7212
|
+
});
|
|
7213
|
+
fleetNotifier.notify();
|
|
7214
|
+
} catch (err) {
|
|
7215
|
+
logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
|
|
7216
|
+
}
|
|
7217
|
+
};
|
|
7218
|
+
await register(activeSessionId);
|
|
7219
|
+
statusTracker.start();
|
|
7220
|
+
const recoveryLock = opts.manageRecoveryLock === false ? void 0 : new RecoveryLock({ dir: paths.projectSessions, sessionStore: opts.sessionStore });
|
|
7221
|
+
let ownsRecoveryLock = false;
|
|
7222
|
+
if (recoveryLock) {
|
|
7223
|
+
try {
|
|
7224
|
+
await recoveryLock.write(activeSessionId);
|
|
7225
|
+
ownsRecoveryLock = true;
|
|
7226
|
+
} catch {
|
|
7227
|
+
}
|
|
7228
|
+
}
|
|
7229
|
+
let stopHqBridges = () => {
|
|
7230
|
+
};
|
|
7231
|
+
let closeHqPublisher = () => {
|
|
7232
|
+
};
|
|
7233
|
+
let restartHqBridges = (_sessionId) => {
|
|
7234
|
+
};
|
|
7235
|
+
if (opts.enableHqTelemetry !== false) {
|
|
7236
|
+
try {
|
|
7237
|
+
const core = await import("@wrongstack/core");
|
|
7238
|
+
const publisher = core.createHqPublisherFromEnv({
|
|
7239
|
+
clientKind: "webui",
|
|
7240
|
+
projectRoot: paths.projectRoot,
|
|
7241
|
+
projectName: path14.basename(paths.projectRoot),
|
|
7242
|
+
appConfig: opts.config,
|
|
7243
|
+
socketFactory: (url) => new WebSocket2(url)
|
|
7244
|
+
});
|
|
7245
|
+
if (publisher) {
|
|
7246
|
+
publisher.connect();
|
|
7247
|
+
closeHqPublisher = () => publisher.close();
|
|
7248
|
+
restartHqBridges = (sessionId) => {
|
|
7249
|
+
stopHqBridges();
|
|
7250
|
+
const stops = [];
|
|
7251
|
+
const add = (start) => {
|
|
7252
|
+
try {
|
|
7253
|
+
stops.push(start());
|
|
7254
|
+
} catch {
|
|
7255
|
+
}
|
|
7256
|
+
};
|
|
7257
|
+
add(
|
|
7258
|
+
() => core.startSessionTelemetryBridge({
|
|
7259
|
+
publisher,
|
|
7260
|
+
events,
|
|
7261
|
+
sessionId,
|
|
7262
|
+
projectRoot: paths.projectRoot,
|
|
7263
|
+
projectName: path14.basename(paths.projectRoot),
|
|
7264
|
+
globalRoot: paths.globalRoot,
|
|
7265
|
+
initialAgents: statusTracker.getAgents(),
|
|
7266
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7267
|
+
})
|
|
7268
|
+
);
|
|
7269
|
+
add(
|
|
7270
|
+
() => core.startFleetTelemetryBridge({
|
|
7271
|
+
events,
|
|
7272
|
+
publisher,
|
|
7273
|
+
runId: sessionId,
|
|
7274
|
+
sessionId
|
|
7275
|
+
})
|
|
7276
|
+
);
|
|
7277
|
+
add(() => core.startBrainTelemetryBridge({ events, publisher, sessionId }));
|
|
7278
|
+
add(() => core.startWorktreeTelemetryBridge({ events, publisher, sessionId }));
|
|
7279
|
+
add(
|
|
7280
|
+
() => core.startToolTelemetryBridge({
|
|
7281
|
+
events,
|
|
7282
|
+
publisher,
|
|
7283
|
+
projectRoot: paths.projectRoot,
|
|
7284
|
+
sessionId
|
|
7285
|
+
})
|
|
7286
|
+
);
|
|
7287
|
+
add(() => core.startCostTelemetryBridge({ events, publisher, sessionId }));
|
|
7288
|
+
stopHqBridges = () => {
|
|
7289
|
+
for (const stop2 of stops.splice(0)) {
|
|
7290
|
+
try {
|
|
7291
|
+
stop2();
|
|
7292
|
+
} catch {
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
};
|
|
7296
|
+
};
|
|
7297
|
+
restartHqBridges(activeSessionId);
|
|
7298
|
+
}
|
|
7299
|
+
} catch (err) {
|
|
7300
|
+
logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7303
|
+
const repointRecovery = async (sessionId) => {
|
|
7304
|
+
if (!recoveryLock) return;
|
|
7305
|
+
try {
|
|
7306
|
+
if (ownsRecoveryLock) await recoveryLock.clear();
|
|
7307
|
+
await recoveryLock.write(sessionId);
|
|
7308
|
+
ownsRecoveryLock = true;
|
|
7309
|
+
} catch {
|
|
7310
|
+
ownsRecoveryLock = false;
|
|
7311
|
+
}
|
|
7312
|
+
};
|
|
7313
|
+
const activate = async (sessionId) => {
|
|
7314
|
+
if (stopped || sessionId === activeSessionId) return;
|
|
7315
|
+
transition = transition.then(async () => {
|
|
7316
|
+
if (stopped || sessionId === activeSessionId) return;
|
|
7317
|
+
activeSessionId = sessionId;
|
|
7318
|
+
await register(sessionId);
|
|
7319
|
+
await repointRecovery(sessionId);
|
|
7320
|
+
try {
|
|
7321
|
+
restartHqBridges(sessionId);
|
|
7322
|
+
} catch (err) {
|
|
7323
|
+
logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
|
|
7324
|
+
}
|
|
7325
|
+
});
|
|
7326
|
+
await transition;
|
|
7327
|
+
};
|
|
7328
|
+
const stop = async () => {
|
|
7329
|
+
if (stopped) return;
|
|
7330
|
+
stopped = true;
|
|
7331
|
+
await transition.catch(() => void 0);
|
|
7332
|
+
statusTracker.stop();
|
|
7333
|
+
stopHqBridges();
|
|
7334
|
+
closeHqPublisher();
|
|
7335
|
+
fleetNotifier.dispose();
|
|
7336
|
+
try {
|
|
7337
|
+
await registry.markClosing();
|
|
7338
|
+
await registry.unregister();
|
|
7339
|
+
} catch {
|
|
7340
|
+
}
|
|
7341
|
+
if (recoveryLock && ownsRecoveryLock) {
|
|
7342
|
+
await recoveryLock.clear().catch(() => void 0);
|
|
7343
|
+
ownsRecoveryLock = false;
|
|
7344
|
+
}
|
|
7345
|
+
};
|
|
7346
|
+
return { statusTracker, activate, stop };
|
|
7347
|
+
}
|
|
7348
|
+
function errorMessage2(err) {
|
|
7349
|
+
return err instanceof Error ? err.message : String(err);
|
|
7350
|
+
}
|
|
7351
|
+
|
|
6733
7352
|
// src/server/pre-context-services.ts
|
|
6734
7353
|
var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
|
|
6735
7354
|
async function createPreContextServices(input) {
|
|
6736
|
-
const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
|
|
7355
|
+
const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
|
|
6737
7356
|
const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
|
|
6738
7357
|
cacheFile: wpaths.modelsCache,
|
|
6739
7358
|
ttlSeconds: 0,
|
|
@@ -6745,20 +7364,22 @@ async function createPreContextServices(input) {
|
|
|
6745
7364
|
await modelsRegistry.refresh();
|
|
6746
7365
|
logger.info("models.dev catalog refreshed");
|
|
6747
7366
|
} catch (err) {
|
|
6748
|
-
logger.warn(`models.dev refresh failed (${
|
|
7367
|
+
logger.warn(`models.dev refresh failed (${toErrorMessage5(err)}); using cached catalog`);
|
|
6749
7368
|
}
|
|
6750
7369
|
}
|
|
6751
7370
|
try {
|
|
6752
7371
|
await discoverAndMergeWebuiProviders({
|
|
6753
7372
|
config,
|
|
6754
7373
|
registry: modelsRegistry,
|
|
6755
|
-
cacheDir:
|
|
7374
|
+
cacheDir: path15.dirname(wpaths.modelsCache),
|
|
6756
7375
|
logger
|
|
6757
7376
|
});
|
|
6758
7377
|
} catch (err) {
|
|
6759
|
-
logger.debug(`provider auto-discovery skipped: ${
|
|
7378
|
+
logger.debug(`provider auto-discovery skipped: ${toErrorMessage5(err)}`);
|
|
6760
7379
|
}
|
|
6761
|
-
const
|
|
7380
|
+
const events = opts.services?.events ?? new EventBus();
|
|
7381
|
+
events.setLogger(logger);
|
|
7382
|
+
const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry, events });
|
|
6762
7383
|
const configStore = opts.services?.configStore ?? container.resolve(TOKENS.ConfigStore);
|
|
6763
7384
|
const providerRegistry = new ProviderRegistry();
|
|
6764
7385
|
try {
|
|
@@ -6766,22 +7387,23 @@ async function createPreContextServices(input) {
|
|
|
6766
7387
|
for (const f of factories) providerRegistry.register(f);
|
|
6767
7388
|
console.log("[WebUI] Provider registry loaded:", providerRegistry.list().length, "providers");
|
|
6768
7389
|
} catch (err) {
|
|
6769
|
-
console.warn(JSON.stringify({ level: "warn", event: "webui.provider_registry_load_failed", message:
|
|
7390
|
+
console.warn(JSON.stringify({ level: "warn", event: "webui.provider_registry_load_failed", message: toErrorMessage5(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
6770
7391
|
}
|
|
6771
7392
|
const toolRegistry = opts.services?.toolRegistry ?? (() => {
|
|
6772
7393
|
const r = new ToolRegistry();
|
|
6773
7394
|
r.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
6774
7395
|
return r;
|
|
6775
7396
|
})();
|
|
6776
|
-
const memoryStore =
|
|
7397
|
+
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
6777
7398
|
if (config.features.memory) {
|
|
6778
7399
|
toolRegistry.register(rememberTool(memoryStore));
|
|
6779
7400
|
toolRegistry.register(forgetTool(memoryStore));
|
|
6780
7401
|
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
6781
7402
|
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7403
|
+
if (isSuperMemoryService(memoryStore)) {
|
|
7404
|
+
for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
|
|
7405
|
+
}
|
|
6782
7406
|
}
|
|
6783
|
-
const events = opts.services?.events ?? new EventBus();
|
|
6784
|
-
events.setLogger(logger);
|
|
6785
7407
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
6786
7408
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
6787
7409
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
@@ -6790,11 +7412,25 @@ async function createPreContextServices(input) {
|
|
|
6790
7412
|
applyToolResultRenderModes(toolRegistry, config.tools?.resultRenderMode);
|
|
6791
7413
|
configureExecPolicy(config.tools?.exec ?? {});
|
|
6792
7414
|
configureDangerBypass(config.tools?.exec?.danger ?? {});
|
|
7415
|
+
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
6793
7416
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
6794
|
-
const
|
|
7417
|
+
const mcpTokenStore = new MCPVaultTokenStore(
|
|
7418
|
+
path15.join(wpaths.projectDir, "mcp-auth.json"),
|
|
7419
|
+
vault
|
|
7420
|
+
);
|
|
7421
|
+
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
7422
|
+
const mcpRegistry = new MCPRegistry({
|
|
7423
|
+
toolRegistry,
|
|
7424
|
+
events,
|
|
7425
|
+
log: logger,
|
|
7426
|
+
cacheDir: wpaths.cacheDir,
|
|
7427
|
+
authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
|
|
7428
|
+
store: mcpTokenStore
|
|
7429
|
+
}),
|
|
7430
|
+
authorizationManager: mcpAuthorizationManager
|
|
7431
|
+
});
|
|
6795
7432
|
if (config.features.mcp && config.mcpServers) {
|
|
6796
7433
|
for (const [name2, cfg] of Object.entries(config.mcpServers)) {
|
|
6797
|
-
if (cfg.enabled === false) continue;
|
|
6798
7434
|
void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
|
|
6799
7435
|
logger.warn(`MCP server "${name2}" failed to start at boot`, err);
|
|
6800
7436
|
});
|
|
@@ -6809,7 +7445,7 @@ async function createPreContextServices(input) {
|
|
|
6809
7445
|
// surface via the SessionRegistry.
|
|
6810
7446
|
isSessionInUse: async (sessionId) => {
|
|
6811
7447
|
try {
|
|
6812
|
-
const registry =
|
|
7448
|
+
const registry = getSessionRegistry2(wpaths.globalRoot);
|
|
6813
7449
|
const live = await registry.listByProject(wpaths.projectSlug);
|
|
6814
7450
|
const hit = live.find((e) => e.sessionId === sessionId);
|
|
6815
7451
|
if (hit) {
|
|
@@ -6834,120 +7470,22 @@ async function createPreContextServices(input) {
|
|
|
6834
7470
|
await input.touchProject(projectRoot, workingDir);
|
|
6835
7471
|
} catch {
|
|
6836
7472
|
}
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
7473
|
+
const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
|
|
7474
|
+
config,
|
|
7475
|
+
events,
|
|
7476
|
+
logger,
|
|
7477
|
+
paths: {
|
|
7478
|
+
globalRoot: wpaths.globalRoot,
|
|
6843
7479
|
projectRoot,
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
registry,
|
|
6854
|
-
sessionId: () => session.id,
|
|
6855
|
-
onUpdate: () => fleetNotifier.notify()
|
|
6856
|
-
});
|
|
6857
|
-
statusTracker.start();
|
|
6858
|
-
let stopHqSessionBridge;
|
|
6859
|
-
let hqTelemetryPublisher;
|
|
6860
|
-
const stopHqAuxBridges = [];
|
|
6861
|
-
try {
|
|
6862
|
-
const {
|
|
6863
|
-
createHqPublisherFromEnv,
|
|
6864
|
-
startSessionTelemetryBridge,
|
|
6865
|
-
startFleetTelemetryBridge,
|
|
6866
|
-
startBrainTelemetryBridge,
|
|
6867
|
-
startWorktreeTelemetryBridge,
|
|
6868
|
-
startToolTelemetryBridge,
|
|
6869
|
-
startCostTelemetryBridge
|
|
6870
|
-
} = await import('@wrongstack/core');
|
|
6871
|
-
const hqTelemetry = createHqPublisherFromEnv({
|
|
6872
|
-
clientKind: "webui",
|
|
6873
|
-
projectRoot,
|
|
6874
|
-
projectName: path5.basename(projectRoot),
|
|
6875
|
-
appConfig: config,
|
|
6876
|
-
socketFactory: (url) => new WebSocket(url)
|
|
6877
|
-
});
|
|
6878
|
-
if (hqTelemetry) {
|
|
6879
|
-
hqTelemetry.connect();
|
|
6880
|
-
hqTelemetryPublisher = hqTelemetry;
|
|
6881
|
-
stopHqSessionBridge = startSessionTelemetryBridge({
|
|
6882
|
-
publisher: hqTelemetry,
|
|
6883
|
-
events,
|
|
6884
|
-
sessionId: session.id,
|
|
6885
|
-
projectRoot,
|
|
6886
|
-
projectName: path5.basename(projectRoot),
|
|
6887
|
-
globalRoot: wpaths.globalRoot,
|
|
6888
|
-
initialAgents: statusTracker?.getAgents(),
|
|
6889
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6890
|
-
});
|
|
6891
|
-
try {
|
|
6892
|
-
stopHqAuxBridges.push(
|
|
6893
|
-
startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
|
|
6894
|
-
);
|
|
6895
|
-
} catch {
|
|
6896
|
-
}
|
|
6897
|
-
try {
|
|
6898
|
-
stopHqAuxBridges.push(
|
|
6899
|
-
startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
6900
|
-
);
|
|
6901
|
-
} catch {
|
|
6902
|
-
}
|
|
6903
|
-
try {
|
|
6904
|
-
stopHqAuxBridges.push(
|
|
6905
|
-
startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
6906
|
-
);
|
|
6907
|
-
} catch {
|
|
6908
|
-
}
|
|
6909
|
-
try {
|
|
6910
|
-
stopHqAuxBridges.push(
|
|
6911
|
-
startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
|
|
6912
|
-
);
|
|
6913
|
-
} catch {
|
|
6914
|
-
}
|
|
6915
|
-
try {
|
|
6916
|
-
stopHqAuxBridges.push(
|
|
6917
|
-
startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
6918
|
-
);
|
|
6919
|
-
} catch {
|
|
6920
|
-
}
|
|
6921
|
-
}
|
|
6922
|
-
} catch {
|
|
6923
|
-
}
|
|
6924
|
-
const stopTracking = async () => {
|
|
6925
|
-
try {
|
|
6926
|
-
fleetNotifier.dispose();
|
|
6927
|
-
await registry.markClosing();
|
|
6928
|
-
statusTracker?.stop();
|
|
6929
|
-
stopHqSessionBridge?.();
|
|
6930
|
-
for (const stop of stopHqAuxBridges) {
|
|
6931
|
-
try {
|
|
6932
|
-
stop();
|
|
6933
|
-
} catch {
|
|
6934
|
-
}
|
|
6935
|
-
}
|
|
6936
|
-
hqTelemetryPublisher?.close();
|
|
6937
|
-
} catch {
|
|
6938
|
-
}
|
|
6939
|
-
};
|
|
6940
|
-
process.once("beforeExit", () => {
|
|
6941
|
-
void stopTracking();
|
|
6942
|
-
});
|
|
6943
|
-
process.once("SIGINT", () => {
|
|
6944
|
-
void stopTracking();
|
|
6945
|
-
});
|
|
6946
|
-
process.once("SIGTERM", () => {
|
|
6947
|
-
void stopTracking();
|
|
6948
|
-
});
|
|
6949
|
-
} catch {
|
|
6950
|
-
}
|
|
7480
|
+
projectSlug: wpaths.projectSlug,
|
|
7481
|
+
projectSessions: wpaths.projectSessions
|
|
7482
|
+
},
|
|
7483
|
+
workingDir,
|
|
7484
|
+
initialSessionId: session.id,
|
|
7485
|
+
sessionStore,
|
|
7486
|
+
manageRecoveryLock: !opts.services?.session
|
|
7487
|
+
});
|
|
7488
|
+
const statusTracker = sessionIdentity.statusTracker;
|
|
6951
7489
|
let context;
|
|
6952
7490
|
const tokenCounter = new DefaultTokenCounter({
|
|
6953
7491
|
registry: modelsRegistry,
|
|
@@ -6972,7 +7510,7 @@ async function createPreContextServices(input) {
|
|
|
6972
7510
|
const modelCapabilitiesRef = { current: modelCapabilities };
|
|
6973
7511
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
6974
7512
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
6975
|
-
manifestPath:
|
|
7513
|
+
manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
|
|
6976
7514
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
6977
7515
|
globalSkillsDir: wpaths.globalSkills,
|
|
6978
7516
|
projectHash: wpaths.projectHash,
|
|
@@ -6981,8 +7519,8 @@ async function createPreContextServices(input) {
|
|
|
6981
7519
|
const promptsEnabled = config.features.prompts !== false;
|
|
6982
7520
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
6983
7521
|
try {
|
|
6984
|
-
const req =
|
|
6985
|
-
return
|
|
7522
|
+
const req = createRequire2(import.meta.url);
|
|
7523
|
+
return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
|
|
6986
7524
|
} catch {
|
|
6987
7525
|
return void 0;
|
|
6988
7526
|
}
|
|
@@ -7034,6 +7572,14 @@ async function createPreContextServices(input) {
|
|
|
7034
7572
|
const initialContextPolicy = resolveContextWindowPolicy(config.context);
|
|
7035
7573
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
7036
7574
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
7575
|
+
context.state.setMeta(
|
|
7576
|
+
"plan.path",
|
|
7577
|
+
sessionScopedPath(wpaths.projectSessions, session.id, ".plan.json")
|
|
7578
|
+
);
|
|
7579
|
+
context.state.setMeta(
|
|
7580
|
+
"task.path",
|
|
7581
|
+
sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
|
|
7582
|
+
);
|
|
7037
7583
|
seedContextMeta(config, context);
|
|
7038
7584
|
return {
|
|
7039
7585
|
modelsRegistry,
|
|
@@ -7050,6 +7596,7 @@ async function createPreContextServices(input) {
|
|
|
7050
7596
|
session,
|
|
7051
7597
|
sessionStartedAt,
|
|
7052
7598
|
statusTracker,
|
|
7599
|
+
sessionIdentity,
|
|
7053
7600
|
tokenCounter,
|
|
7054
7601
|
modeStore,
|
|
7055
7602
|
modeId,
|
|
@@ -7063,8 +7610,33 @@ async function createPreContextServices(input) {
|
|
|
7063
7610
|
needsSetup
|
|
7064
7611
|
};
|
|
7065
7612
|
}
|
|
7613
|
+
function isSuperMemoryService(memoryStore) {
|
|
7614
|
+
const value = memoryStore;
|
|
7615
|
+
return typeof value["retrieveForPath"] === "function" && typeof value["searchSuper"] === "function" && typeof value["graphFor"] === "function" && typeof value["verify"] === "function" && typeof value["hygiene"] === "function" && typeof value["listCandidates"] === "function";
|
|
7616
|
+
}
|
|
7617
|
+
|
|
7618
|
+
// src/server/start-webui.ts
|
|
7619
|
+
import * as path21 from "node:path";
|
|
7620
|
+
import {
|
|
7621
|
+
createDefaultPipelines,
|
|
7622
|
+
createSessionEventBridge,
|
|
7623
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
|
|
7624
|
+
expectDefined as expectDefined3,
|
|
7625
|
+
resolveSessionLoggingConfig,
|
|
7626
|
+
watchProviderConfig
|
|
7627
|
+
} from "@wrongstack/core";
|
|
7628
|
+
import { toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
|
|
7629
|
+
import { makeProviderFromConfig as makeProviderFromConfig3 } from "@wrongstack/providers";
|
|
7630
|
+
import {
|
|
7631
|
+
ensureSessionShell
|
|
7632
|
+
} from "@wrongstack/tools";
|
|
7633
|
+
|
|
7634
|
+
// src/server/boot.ts
|
|
7635
|
+
import {
|
|
7636
|
+
bootConfig as coreBootConfig
|
|
7637
|
+
} from "@wrongstack/core";
|
|
7066
7638
|
async function bootConfig() {
|
|
7067
|
-
const { config, vault, globalConfigPath, projectRoot, wpaths, logger } = await
|
|
7639
|
+
const { config, vault, globalConfigPath, projectRoot, wpaths, logger } = await coreBootConfig({
|
|
7068
7640
|
appLabel: "WebUI"
|
|
7069
7641
|
});
|
|
7070
7642
|
return { config, vault, globalConfigPath, projectRoot, wpaths, logger };
|
|
@@ -7072,14 +7644,46 @@ async function bootConfig() {
|
|
|
7072
7644
|
function patchConfig(config, updates) {
|
|
7073
7645
|
return Object.freeze({ ...config, ...updates });
|
|
7074
7646
|
}
|
|
7647
|
+
|
|
7648
|
+
// src/server/backend-services.ts
|
|
7649
|
+
import { join as join12 } from "node:path";
|
|
7650
|
+
import {
|
|
7651
|
+
Agent,
|
|
7652
|
+
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
7653
|
+
BrainMonitor,
|
|
7654
|
+
CollaborationBus as CollaborationBusCtor,
|
|
7655
|
+
DEFAULT_TOOLS_CONFIG,
|
|
7656
|
+
createStrategyCompactor,
|
|
7657
|
+
collabInjectMiddleware,
|
|
7658
|
+
collabPauseMiddleware,
|
|
7659
|
+
estimateRequestTokensCalibrated,
|
|
7660
|
+
installDesignStudioMiddleware,
|
|
7661
|
+
ObservableBrainArbiter as ObservableBrainArbiterCtor,
|
|
7662
|
+
resolveContextWindowPolicy as resolveContextWindowPolicy2,
|
|
7663
|
+
BrainDecisionLedger,
|
|
7664
|
+
createBrainRuntime,
|
|
7665
|
+
resolveBrainConfigDefaults,
|
|
7666
|
+
EscalationRoutingBrainArbiter,
|
|
7667
|
+
GlobalMailbox as GlobalMailbox2,
|
|
7668
|
+
mailboxSessionTag,
|
|
7669
|
+
SessionMemoryConsolidator,
|
|
7670
|
+
TOKENS as TOKENS2,
|
|
7671
|
+
ToolExecutor
|
|
7672
|
+
} from "@wrongstack/core";
|
|
7673
|
+
|
|
7674
|
+
// src/server/collaboration-ws-handler.ts
|
|
7675
|
+
import { randomUUID } from "node:crypto";
|
|
7676
|
+
import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
7677
|
+
var REPLAY_LIMIT = 50;
|
|
7075
7678
|
var PAUSE_TIMEOUT_MS = 6e4;
|
|
7076
7679
|
var CollaborationWebSocketHandler = class {
|
|
7077
|
-
constructor(events, logger, reader, annotations, bus) {
|
|
7680
|
+
constructor(events, logger, reader, annotations, bus, options = {}) {
|
|
7078
7681
|
this.events = events;
|
|
7079
7682
|
this.logger = logger;
|
|
7080
7683
|
this.reader = reader;
|
|
7081
7684
|
this.annotations = annotations;
|
|
7082
7685
|
this.bus = bus;
|
|
7686
|
+
this.options = options;
|
|
7083
7687
|
this.subscribe();
|
|
7084
7688
|
this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
|
|
7085
7689
|
}
|
|
@@ -7088,6 +7692,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7088
7692
|
reader;
|
|
7089
7693
|
annotations;
|
|
7090
7694
|
bus;
|
|
7695
|
+
options;
|
|
7091
7696
|
clients = /* @__PURE__ */ new Set();
|
|
7092
7697
|
/** sessionId → participants currently watching it. */
|
|
7093
7698
|
bySession = /* @__PURE__ */ new Map();
|
|
@@ -7115,11 +7720,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
7115
7720
|
handleMessage(ws, msg) {
|
|
7116
7721
|
if (msg.type === "collab.join") {
|
|
7117
7722
|
const payload = msg.payload;
|
|
7118
|
-
if (
|
|
7723
|
+
if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
|
|
7119
7724
|
this.send(ws, this.errorMessage("collab.join requires sessionId"));
|
|
7120
7725
|
return true;
|
|
7121
7726
|
}
|
|
7122
|
-
|
|
7727
|
+
const role = payload.role ?? "observer";
|
|
7728
|
+
if (role !== "observer" && role !== "annotator" && role !== "controller") {
|
|
7729
|
+
this.send(ws, this.errorMessage(`unknown collaboration role '${String(role)}'`));
|
|
7730
|
+
return true;
|
|
7731
|
+
}
|
|
7732
|
+
this.join(ws, payload.sessionId, role);
|
|
7123
7733
|
return true;
|
|
7124
7734
|
}
|
|
7125
7735
|
if (msg.type === "collab.leave") {
|
|
@@ -7154,6 +7764,20 @@ var CollaborationWebSocketHandler = class {
|
|
|
7154
7764
|
}
|
|
7155
7765
|
// ── Join / leave flow ──────────────────────────────────────────────────
|
|
7156
7766
|
join(ws, sessionId, role) {
|
|
7767
|
+
const activeSessionId = this.options.getActiveSessionId?.();
|
|
7768
|
+
if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
|
|
7769
|
+
this.send(
|
|
7770
|
+
ws,
|
|
7771
|
+
this.errorMessage(
|
|
7772
|
+
`collab.join sessionId mismatch (active: ${activeSessionId})`
|
|
7773
|
+
)
|
|
7774
|
+
);
|
|
7775
|
+
return;
|
|
7776
|
+
}
|
|
7777
|
+
if (this.findParticipant(ws)) {
|
|
7778
|
+
this.send(ws, this.errorMessage("collab.join requires leaving the current session first"));
|
|
7779
|
+
return;
|
|
7780
|
+
}
|
|
7157
7781
|
if (role === "controller" && !this.bus) {
|
|
7158
7782
|
this.send(
|
|
7159
7783
|
ws,
|
|
@@ -7172,6 +7796,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
7172
7796
|
);
|
|
7173
7797
|
return;
|
|
7174
7798
|
}
|
|
7799
|
+
if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
|
|
7800
|
+
this.send(
|
|
7801
|
+
ws,
|
|
7802
|
+
this.errorMessage(`role '${role}' requires explicit server authorization`)
|
|
7803
|
+
);
|
|
7804
|
+
return;
|
|
7805
|
+
}
|
|
7175
7806
|
const participant = {
|
|
7176
7807
|
participantId: randomUUID(),
|
|
7177
7808
|
ws,
|
|
@@ -7199,7 +7830,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7199
7830
|
if (this.reader) {
|
|
7200
7831
|
this.replayHistory(ws, sessionId).catch((err) => {
|
|
7201
7832
|
this.logger.debug?.(
|
|
7202
|
-
`collab: replay failed for ${sessionId}: ${
|
|
7833
|
+
`collab: replay failed for ${sessionId}: ${toErrorMessage6(err)}`
|
|
7203
7834
|
);
|
|
7204
7835
|
});
|
|
7205
7836
|
}
|
|
@@ -7317,7 +7948,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7317
7948
|
this.send(
|
|
7318
7949
|
ws,
|
|
7319
7950
|
this.errorMessage(
|
|
7320
|
-
`annotation rejected: ${
|
|
7951
|
+
`annotation rejected: ${toErrorMessage6(err)}`
|
|
7321
7952
|
)
|
|
7322
7953
|
);
|
|
7323
7954
|
}
|
|
@@ -7384,7 +8015,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7384
8015
|
this.send(
|
|
7385
8016
|
ws,
|
|
7386
8017
|
this.errorMessage(
|
|
7387
|
-
`resolve failed: ${
|
|
8018
|
+
`resolve failed: ${toErrorMessage6(err)}`
|
|
7388
8019
|
)
|
|
7389
8020
|
);
|
|
7390
8021
|
}
|
|
@@ -7425,17 +8056,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
7425
8056
|
type: "collab.event",
|
|
7426
8057
|
payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7427
8058
|
};
|
|
7428
|
-
const
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
`collab broadcast failed: ${toErrorMessage(err)}`
|
|
7436
|
-
);
|
|
7437
|
-
}
|
|
7438
|
-
}
|
|
8059
|
+
const activeSessionId = this.options.getActiveSessionId?.();
|
|
8060
|
+
if (activeSessionId !== void 0) {
|
|
8061
|
+
this.broadcast(activeSessionId, msg);
|
|
8062
|
+
return;
|
|
8063
|
+
}
|
|
8064
|
+
for (const sessionId of this.bySession.keys()) {
|
|
8065
|
+
this.broadcast(sessionId, msg);
|
|
7439
8066
|
}
|
|
7440
8067
|
}
|
|
7441
8068
|
/**
|
|
@@ -7459,11 +8086,11 @@ var CollaborationWebSocketHandler = class {
|
|
|
7459
8086
|
}
|
|
7460
8087
|
} catch (err) {
|
|
7461
8088
|
this.logger.debug?.(
|
|
7462
|
-
`collab: session reader rejected ${sessionId}: ${
|
|
8089
|
+
`collab: session reader rejected ${sessionId}: ${toErrorMessage6(err)}`
|
|
7463
8090
|
);
|
|
7464
8091
|
return;
|
|
7465
8092
|
}
|
|
7466
|
-
const tail2 = all.slice(-
|
|
8093
|
+
const tail2 = all.slice(-REPLAY_LIMIT);
|
|
7467
8094
|
if (tail2.length === 0) return;
|
|
7468
8095
|
for (const raw of tail2) {
|
|
7469
8096
|
const ev = raw;
|
|
@@ -7540,7 +8167,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7540
8167
|
if (p.ws.readyState === 1) p.ws.send(data);
|
|
7541
8168
|
} catch (err) {
|
|
7542
8169
|
this.logger.debug?.(
|
|
7543
|
-
`collab broadcast failed: ${
|
|
8170
|
+
`collab broadcast failed: ${toErrorMessage6(err)}`
|
|
7544
8171
|
);
|
|
7545
8172
|
}
|
|
7546
8173
|
}
|
|
@@ -7759,7 +8386,8 @@ var CollaborationWebSocketHandler = class {
|
|
|
7759
8386
|
* Bus callback: a queued injection was spliced into a real tool call. Re-emit
|
|
7760
8387
|
* `collab.injection.granted` with phase `'consumed'` and the now-known tool
|
|
7761
8388
|
* name. The injection carries no sessionId, so resolve it from the author's
|
|
7762
|
-
* current
|
|
8389
|
+
* current participant. If the author has left, fail closed rather than leak
|
|
8390
|
+
* the payload to unrelated sessions.
|
|
7763
8391
|
*/
|
|
7764
8392
|
broadcastInjectionConsumed(info) {
|
|
7765
8393
|
let sessionId = null;
|
|
@@ -7788,10 +8416,23 @@ var CollaborationWebSocketHandler = class {
|
|
|
7788
8416
|
if (sessionId) {
|
|
7789
8417
|
this.broadcast(sessionId, message(sessionId));
|
|
7790
8418
|
} else {
|
|
7791
|
-
|
|
8419
|
+
this.logger.debug?.(
|
|
8420
|
+
`collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
|
|
8421
|
+
);
|
|
7792
8422
|
}
|
|
7793
8423
|
}
|
|
7794
8424
|
};
|
|
8425
|
+
|
|
8426
|
+
// src/server/codebase-indexing.ts
|
|
8427
|
+
import * as fs12 from "node:fs";
|
|
8428
|
+
import * as path16 from "node:path";
|
|
8429
|
+
import {
|
|
8430
|
+
cancelPendingReindexes,
|
|
8431
|
+
enqueueReindex,
|
|
8432
|
+
isIndexableFile,
|
|
8433
|
+
runStartupIndex,
|
|
8434
|
+
shutdownCodebaseIndexHost
|
|
8435
|
+
} from "@wrongstack/tools";
|
|
7795
8436
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
7796
8437
|
"node_modules",
|
|
7797
8438
|
".git",
|
|
@@ -7837,7 +8478,7 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
7837
8478
|
if (!filename) return;
|
|
7838
8479
|
const rel = filename.toString();
|
|
7839
8480
|
if (isIgnored(rel)) return;
|
|
7840
|
-
const abs =
|
|
8481
|
+
const abs = path16.resolve(deps2.projectRoot, rel);
|
|
7841
8482
|
enqueueFile(abs);
|
|
7842
8483
|
});
|
|
7843
8484
|
watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
|
|
@@ -7850,7 +8491,7 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
7850
8491
|
}
|
|
7851
8492
|
function enqueueFile(filePath) {
|
|
7852
8493
|
if (!idx.onEdit && !idx.watchExternal) return;
|
|
7853
|
-
const abs =
|
|
8494
|
+
const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
|
|
7854
8495
|
if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
|
|
7855
8496
|
enqueueReindex({
|
|
7856
8497
|
projectRoot: deps2.projectRoot,
|
|
@@ -7887,15 +8528,23 @@ function isIgnored(rel) {
|
|
|
7887
8528
|
return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
|
|
7888
8529
|
}
|
|
7889
8530
|
function isInside2(root, target) {
|
|
7890
|
-
const normalizedRoot =
|
|
7891
|
-
const normalizedTarget =
|
|
7892
|
-
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot +
|
|
8531
|
+
const normalizedRoot = path16.resolve(root);
|
|
8532
|
+
const normalizedTarget = path16.resolve(target);
|
|
8533
|
+
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
|
|
7893
8534
|
}
|
|
8535
|
+
|
|
8536
|
+
// src/server/discover-mailbox-bridge.ts
|
|
8537
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
8538
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
8539
|
+
import { existsSync } from "node:fs";
|
|
8540
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
8541
|
+
import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
|
|
8542
|
+
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
7894
8543
|
var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
|
|
7895
8544
|
async function discoverMailboxBridgeForWebui(params) {
|
|
7896
8545
|
const mode = params.config?.features?.mailboxBridge ?? "auto";
|
|
7897
8546
|
if (mode === "off") return;
|
|
7898
|
-
const projectDir = resolveProjectDir(params.projectRoot,
|
|
8547
|
+
const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot2());
|
|
7899
8548
|
let result = await readLiveLock(projectDir);
|
|
7900
8549
|
let spawnedPid = null;
|
|
7901
8550
|
if (result.kind !== "live") {
|
|
@@ -7961,7 +8610,7 @@ async function waitForLiveMailboxBridge(projectDir, timeoutMs) {
|
|
|
7961
8610
|
function spawnMailboxBridge(projectRoot, logger) {
|
|
7962
8611
|
const invocation = mailboxServeInvocation(projectRoot);
|
|
7963
8612
|
try {
|
|
7964
|
-
const child =
|
|
8613
|
+
const child = spawn3(invocation.command, invocation.args, {
|
|
7965
8614
|
cwd: projectRoot,
|
|
7966
8615
|
detached: process.platform !== "win32" && !invocation.useShell,
|
|
7967
8616
|
env: process.env,
|
|
@@ -7992,7 +8641,7 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
7992
8641
|
return { command: process.execPath, args: [explicitCliEntry, "mailbox", "serve"] };
|
|
7993
8642
|
}
|
|
7994
8643
|
try {
|
|
7995
|
-
const require2 =
|
|
8644
|
+
const require2 = createRequire3(import.meta.url);
|
|
7996
8645
|
const cliEntry = require2.resolve("@wrongstack/cli");
|
|
7997
8646
|
return { command: process.execPath, args: [cliEntry, "mailbox", "serve"] };
|
|
7998
8647
|
} catch {
|
|
@@ -8016,9 +8665,9 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
8016
8665
|
function findWorkspaceCliEntry(projectRoot) {
|
|
8017
8666
|
let dir = projectRoot;
|
|
8018
8667
|
for (let i = 0; i < 6; i++) {
|
|
8019
|
-
const candidate =
|
|
8668
|
+
const candidate = join11(dir, "packages", "cli", "dist", "index.js");
|
|
8020
8669
|
if (existsSync(candidate)) return candidate;
|
|
8021
|
-
const parent =
|
|
8670
|
+
const parent = dirname7(dir);
|
|
8022
8671
|
if (parent === dir) return null;
|
|
8023
8672
|
dir = parent;
|
|
8024
8673
|
}
|
|
@@ -8027,10 +8676,15 @@ function findWorkspaceCliEntry(projectRoot) {
|
|
|
8027
8676
|
function sleep(ms) {
|
|
8028
8677
|
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
8029
8678
|
}
|
|
8679
|
+
|
|
8680
|
+
// src/server/terminal-ws-handler.ts
|
|
8681
|
+
import { spawn as spawnChild } from "node:child_process";
|
|
8682
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
8683
|
+
import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
|
|
8030
8684
|
var MAX_SESSIONS_PER_CLIENT = 8;
|
|
8031
8685
|
var DEFAULT_COLS = 80;
|
|
8032
8686
|
var DEFAULT_ROWS = 24;
|
|
8033
|
-
var requireFromHere =
|
|
8687
|
+
var requireFromHere = createRequire4(import.meta.url);
|
|
8034
8688
|
var cachedNodePty;
|
|
8035
8689
|
var TerminalWebSocketHandler = class {
|
|
8036
8690
|
constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
|
|
@@ -8108,8 +8762,8 @@ var TerminalWebSocketHandler = class {
|
|
|
8108
8762
|
...windowsPtyOptions()
|
|
8109
8763
|
});
|
|
8110
8764
|
} catch (err) {
|
|
8111
|
-
const msg = `Integrated terminal failed to start: ${
|
|
8112
|
-
this.logger.warn?.(`terminal spawn failed: ${
|
|
8765
|
+
const msg = `Integrated terminal failed to start: ${toErrorMessage7(err)}`;
|
|
8766
|
+
this.logger.warn?.(`terminal spawn failed: ${toErrorMessage7(err)}`);
|
|
8113
8767
|
this.send(ws, { type: "terminal.output", payload: { id: payload.id, data: `${msg}\r
|
|
8114
8768
|
` } });
|
|
8115
8769
|
this.send(ws, { type: "terminal.exit", payload: { id: payload.id, exitCode: -1 } });
|
|
@@ -8162,7 +8816,7 @@ var TerminalWebSocketHandler = class {
|
|
|
8162
8816
|
try {
|
|
8163
8817
|
pty.kill();
|
|
8164
8818
|
} catch (err) {
|
|
8165
|
-
this.logger.warn?.(`${reason} failed: ${
|
|
8819
|
+
this.logger.warn?.(`${reason} failed: ${toErrorMessage7(err)}`);
|
|
8166
8820
|
}
|
|
8167
8821
|
}
|
|
8168
8822
|
killWindowsProcessTree(pty, reason) {
|
|
@@ -8174,7 +8828,7 @@ var TerminalWebSocketHandler = class {
|
|
|
8174
8828
|
} catch {
|
|
8175
8829
|
}
|
|
8176
8830
|
} catch (err) {
|
|
8177
|
-
this.logger.warn?.(`${reason} taskkill failed: ${
|
|
8831
|
+
this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage7(err)}`);
|
|
8178
8832
|
}
|
|
8179
8833
|
}
|
|
8180
8834
|
send(ws, msg) {
|
|
@@ -8205,7 +8859,7 @@ function windowsPtyOptions() {
|
|
|
8205
8859
|
return { useConptyDll: true };
|
|
8206
8860
|
}
|
|
8207
8861
|
function defaultKillProcessTree(pid) {
|
|
8208
|
-
const child =
|
|
8862
|
+
const child = spawnChild("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
8209
8863
|
stdio: "ignore",
|
|
8210
8864
|
windowsHide: true
|
|
8211
8865
|
});
|
|
@@ -8218,6 +8872,14 @@ function clampDim(value, fallback) {
|
|
|
8218
8872
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
8219
8873
|
return Math.max(1, Math.min(1e3, Math.floor(value)));
|
|
8220
8874
|
}
|
|
8875
|
+
|
|
8876
|
+
// src/server/backend-services.ts
|
|
8877
|
+
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
8878
|
+
import {
|
|
8879
|
+
createSuperMemoryToolCallMiddleware,
|
|
8880
|
+
createSuperMemoryTurnMiddleware
|
|
8881
|
+
} from "@wrongstack/super-memory";
|
|
8882
|
+
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
8221
8883
|
async function createAgentServices(input) {
|
|
8222
8884
|
const {
|
|
8223
8885
|
config,
|
|
@@ -8232,15 +8894,37 @@ async function createAgentServices(input) {
|
|
|
8232
8894
|
providerRegistry,
|
|
8233
8895
|
modelsRegistry,
|
|
8234
8896
|
events,
|
|
8897
|
+
memoryStore,
|
|
8235
8898
|
modelCapabilitiesRef
|
|
8236
8899
|
} = input;
|
|
8237
|
-
const collabBus = new
|
|
8900
|
+
const collabBus = new CollaborationBusCtor();
|
|
8238
8901
|
const pipelines = input.pipelines;
|
|
8239
8902
|
const collabInject = collabInjectMiddleware(collabBus, { logger });
|
|
8240
8903
|
pipelines.toolCall.prepend(collabInject);
|
|
8241
8904
|
const collabPause = collabPauseMiddleware(collabBus, { logger });
|
|
8242
8905
|
pipelines.toolCall.prepend(collabPause);
|
|
8243
8906
|
installDesignStudioMiddleware({ pipelines, ctx: context });
|
|
8907
|
+
if (config.features.memory !== false && config.superMemory?.enabled !== false && isSuperMemoryRetriever(memoryStore)) {
|
|
8908
|
+
if (config.superMemory?.inject?.toolResults !== false) {
|
|
8909
|
+
pipelines.toolCall.use(createSuperMemoryToolCallMiddleware({
|
|
8910
|
+
memory: memoryStore,
|
|
8911
|
+
maxHintsPerTool: config.superMemory?.inject?.maxHintsPerTool,
|
|
8912
|
+
maxCharsPerTool: config.superMemory?.inject?.maxCharsPerTool,
|
|
8913
|
+
minScore: config.superMemory?.inject?.minScore,
|
|
8914
|
+
repeatCooldownMs: config.superMemory?.inject?.repeatCooldownMs,
|
|
8915
|
+
verifyOnMutation: config.superMemory?.hygiene?.autoOnFileChange,
|
|
8916
|
+
triggers: config.superMemory?.inject?.triggers
|
|
8917
|
+
}));
|
|
8918
|
+
}
|
|
8919
|
+
if (config.superMemory?.inject?.turnContext !== false) {
|
|
8920
|
+
pipelines.request.use(createSuperMemoryTurnMiddleware({
|
|
8921
|
+
memory: memoryStore,
|
|
8922
|
+
maxMemories: config.superMemory?.inject?.maxTurnMemories,
|
|
8923
|
+
maxChars: config.superMemory?.inject?.maxCharsPerTurn,
|
|
8924
|
+
minScore: config.superMemory?.inject?.minScore
|
|
8925
|
+
}));
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8244
8928
|
const codebaseIndexing = setupWebUICodebaseIndexing({
|
|
8245
8929
|
config,
|
|
8246
8930
|
context,
|
|
@@ -8254,7 +8938,7 @@ async function createAgentServices(input) {
|
|
|
8254
8938
|
summarizerModel: config.context?.summarizerModel,
|
|
8255
8939
|
llmSelector: config.context?.llmSelector
|
|
8256
8940
|
});
|
|
8257
|
-
const initialContextPolicy =
|
|
8941
|
+
const initialContextPolicy = resolveContextWindowPolicy2(config.context);
|
|
8258
8942
|
let autoCompactor;
|
|
8259
8943
|
if (config.context?.autoCompact !== false) {
|
|
8260
8944
|
let effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
@@ -8271,7 +8955,7 @@ async function createAgentServices(input) {
|
|
|
8271
8955
|
}
|
|
8272
8956
|
}
|
|
8273
8957
|
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
8274
|
-
autoCompactor = new
|
|
8958
|
+
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
8275
8959
|
compactor,
|
|
8276
8960
|
effectiveMaxContext,
|
|
8277
8961
|
(ctx) => estimateRequestTokensCalibrated(
|
|
@@ -8299,7 +8983,7 @@ async function createAgentServices(input) {
|
|
|
8299
8983
|
const updateAutoCompactionMaxContext = async (newProvider, providerId = newProvider.id, providerCfg) => {
|
|
8300
8984
|
await modelsRegistry.refresh().catch((err) => {
|
|
8301
8985
|
logger.warn(
|
|
8302
|
-
`models.dev refresh failed for ${providerId}/${context.model}: ${
|
|
8986
|
+
`models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
|
|
8303
8987
|
);
|
|
8304
8988
|
});
|
|
8305
8989
|
const currentConfig = input.config;
|
|
@@ -8336,9 +9020,9 @@ async function createAgentServices(input) {
|
|
|
8336
9020
|
maxContext: newMaxContext
|
|
8337
9021
|
});
|
|
8338
9022
|
};
|
|
8339
|
-
const secretScrubber = container.resolve(
|
|
8340
|
-
const renderer = container.has(
|
|
8341
|
-
const permissionPolicy = container.resolve(
|
|
9023
|
+
const secretScrubber = container.resolve(TOKENS2.SecretScrubber);
|
|
9024
|
+
const renderer = container.has(TOKENS2.Renderer) ? container.resolve(TOKENS2.Renderer) : void 0;
|
|
9025
|
+
const permissionPolicy = container.resolve(TOKENS2.PermissionPolicy);
|
|
8342
9026
|
const toolExecutor = new ToolExecutor(toolRegistry, {
|
|
8343
9027
|
permissionPolicy,
|
|
8344
9028
|
secretScrubber,
|
|
@@ -8349,7 +9033,7 @@ async function createAgentServices(input) {
|
|
|
8349
9033
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
8350
9034
|
tracer: void 0
|
|
8351
9035
|
});
|
|
8352
|
-
const webuiLogger = container.resolve(
|
|
9036
|
+
const webuiLogger = container.resolve(TOKENS2.Logger);
|
|
8353
9037
|
void discoverMailboxBridgeForWebui({
|
|
8354
9038
|
projectRoot,
|
|
8355
9039
|
config,
|
|
@@ -8375,25 +9059,66 @@ async function createAgentServices(input) {
|
|
|
8375
9059
|
confirmAwaiter: void 0,
|
|
8376
9060
|
toolExecutor
|
|
8377
9061
|
});
|
|
9062
|
+
if (config.features.memory && config.features.memoryConsolidation !== false) {
|
|
9063
|
+
agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
|
|
9064
|
+
}
|
|
8378
9065
|
console.log("[WebUI] Agent initialized");
|
|
8379
|
-
const
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
9066
|
+
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
9067
|
+
fallbackModels: config.fallbackModels
|
|
9068
|
+
});
|
|
9069
|
+
const brainLedgerPath = join12(wpaths.projectDir, "brain-ledger.jsonl");
|
|
9070
|
+
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
9071
|
+
let brainLedger;
|
|
9072
|
+
const startBrainLedger = () => {
|
|
9073
|
+
if (brainLedger) return;
|
|
9074
|
+
brainLedger = new BrainDecisionLedger({ events, filePath: brainLedgerPath });
|
|
9075
|
+
void brainLedger.start();
|
|
8387
9076
|
};
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
9077
|
+
if (brainLedgerEnabled) startBrainLedger();
|
|
9078
|
+
const brainRuntime = createBrainRuntime({
|
|
9079
|
+
initialConfig: brainCfg,
|
|
9080
|
+
defaultProviderId: config.provider,
|
|
9081
|
+
sessionProvider: () => provider,
|
|
9082
|
+
sessionModel: () => context.model,
|
|
9083
|
+
resolveProvider: (providerId) => {
|
|
9084
|
+
const savedCfg = config.providers?.[providerId] ?? {};
|
|
9085
|
+
return providerRegistry.create({
|
|
9086
|
+
...savedCfg,
|
|
9087
|
+
apiKey: savedCfg.apiKey ?? config.apiKey,
|
|
9088
|
+
baseUrl: savedCfg.baseUrl ?? config.baseUrl,
|
|
9089
|
+
type: providerId
|
|
9090
|
+
});
|
|
9091
|
+
},
|
|
9092
|
+
ledger: {
|
|
9093
|
+
getPath: () => brainLedgerEnabled ? brainLedgerPath : void 0,
|
|
9094
|
+
isEnabled: () => brainLedgerEnabled,
|
|
9095
|
+
setEnabled: (on) => {
|
|
9096
|
+
brainLedgerEnabled = on;
|
|
9097
|
+
if (on) {
|
|
9098
|
+
startBrainLedger();
|
|
9099
|
+
} else {
|
|
9100
|
+
void brainLedger?.stop();
|
|
9101
|
+
brainLedger = void 0;
|
|
9102
|
+
}
|
|
9103
|
+
},
|
|
9104
|
+
failureStreakFor: (request) => brainLedger?.failureStreakFor(request) ?? 0,
|
|
9105
|
+
getDecisionDigest: (request) => brainLedger?.digestFor(request)
|
|
9106
|
+
},
|
|
9107
|
+
persist: input.persistBrainConfig
|
|
9108
|
+
});
|
|
9109
|
+
const brainSettings = {
|
|
9110
|
+
get maxAutoRisk() {
|
|
9111
|
+
return brainRuntime.getMaxAutoRisk();
|
|
9112
|
+
},
|
|
9113
|
+
set maxAutoRisk(level) {
|
|
9114
|
+
void brainRuntime.apply({ maxAutoRisk: level }).persisted;
|
|
9115
|
+
}
|
|
9116
|
+
};
|
|
9117
|
+
const brain = new ObservableBrainArbiterCtor(
|
|
9118
|
+
new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
|
|
8394
9119
|
events
|
|
8395
9120
|
);
|
|
8396
|
-
container.bind(
|
|
9121
|
+
container.bind(TOKENS2.BrainArbiter, () => brain);
|
|
8397
9122
|
const brainLog = [];
|
|
8398
9123
|
const pushBrainLog = (entry) => {
|
|
8399
9124
|
brainLog.push(entry);
|
|
@@ -8426,10 +9151,16 @@ async function createAgentServices(input) {
|
|
|
8426
9151
|
outcome: e.decision.type === "deny" ? e.decision.reason : ""
|
|
8427
9152
|
})
|
|
8428
9153
|
);
|
|
8429
|
-
const brainMailbox = new
|
|
9154
|
+
const brainMailbox = new GlobalMailbox2(wpaths.projectDir, events);
|
|
8430
9155
|
const brainMonitor = new BrainMonitor({
|
|
8431
9156
|
events,
|
|
8432
9157
|
brain,
|
|
9158
|
+
toolFailureStreak: brainCfg.monitor?.toolFailureStreak,
|
|
9159
|
+
errorStormCount: brainCfg.monitor?.errorStormCount,
|
|
9160
|
+
stallMs: brainCfg.monitor?.stallMs,
|
|
9161
|
+
fileChurnThreshold: brainCfg.monitor?.fileChurnThreshold,
|
|
9162
|
+
fileChurnWindowMs: brainCfg.monitor?.fileChurnWindowMs,
|
|
9163
|
+
cooldownMs: brainCfg.monitor?.cooldownMs,
|
|
8433
9164
|
sessionId: () => context.session?.id,
|
|
8434
9165
|
intervene: async ({ subject, body }) => {
|
|
8435
9166
|
const tag = mailboxSessionTag(input.sessionGetter().id);
|
|
@@ -8494,7 +9225,10 @@ async function createAgentServices(input) {
|
|
|
8494
9225
|
logger,
|
|
8495
9226
|
input.sessionReader,
|
|
8496
9227
|
input.annotationsStore,
|
|
8497
|
-
collabBus
|
|
9228
|
+
collabBus,
|
|
9229
|
+
{
|
|
9230
|
+
getActiveSessionId: () => context.session.id
|
|
9231
|
+
}
|
|
8498
9232
|
);
|
|
8499
9233
|
return {
|
|
8500
9234
|
collabBus,
|
|
@@ -8506,8 +9240,13 @@ async function createAgentServices(input) {
|
|
|
8506
9240
|
pipelines,
|
|
8507
9241
|
brain,
|
|
8508
9242
|
brainSettings,
|
|
9243
|
+
brainRuntime,
|
|
8509
9244
|
brainLog,
|
|
8510
9245
|
brainMonitor,
|
|
9246
|
+
// Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
|
|
9247
|
+
get brainLedger() {
|
|
9248
|
+
return brainLedger;
|
|
9249
|
+
},
|
|
8511
9250
|
codebaseIndexing,
|
|
8512
9251
|
autoPhaseHandler,
|
|
8513
9252
|
specsHandler,
|
|
@@ -8519,14 +9258,14 @@ async function createAgentServices(input) {
|
|
|
8519
9258
|
updateAutoCompactionMaxContext
|
|
8520
9259
|
};
|
|
8521
9260
|
}
|
|
9261
|
+
function isSuperMemoryRetriever(memoryStore) {
|
|
9262
|
+
const value = memoryStore;
|
|
9263
|
+
return typeof value["retrieveForPath"] === "function" && typeof value["searchSuper"] === "function";
|
|
9264
|
+
}
|
|
8522
9265
|
|
|
8523
9266
|
// src/server/pending-confirms.ts
|
|
8524
|
-
function isDestructivePendingConfirm(confirm) {
|
|
8525
|
-
return confirm.riskTier === "destructive" || confirm.decisionSource === "yolo_destructive";
|
|
8526
|
-
}
|
|
8527
9267
|
function resolveYoloEligiblePendingConfirms(pendingConfirms) {
|
|
8528
9268
|
for (const [id, confirm] of pendingConfirms) {
|
|
8529
|
-
if (isDestructivePendingConfirm(confirm)) continue;
|
|
8530
9269
|
pendingConfirms.delete(id);
|
|
8531
9270
|
confirm.resolve("yes");
|
|
8532
9271
|
}
|
|
@@ -8682,6 +9421,20 @@ function createConnectionHandler(opts) {
|
|
|
8682
9421
|
};
|
|
8683
9422
|
}
|
|
8684
9423
|
|
|
9424
|
+
// src/server/message-dispatcher.ts
|
|
9425
|
+
import path17 from "node:path";
|
|
9426
|
+
import {
|
|
9427
|
+
buildUserContentBlocks,
|
|
9428
|
+
IncomingImageError,
|
|
9429
|
+
parseIncomingImages
|
|
9430
|
+
} from "@wrongstack/core/utils";
|
|
9431
|
+
import {
|
|
9432
|
+
createToolVisionAdapters,
|
|
9433
|
+
ImageInputUnsupportedError,
|
|
9434
|
+
routeImagesForModel,
|
|
9435
|
+
VisionUrlBlockedError
|
|
9436
|
+
} from "@wrongstack/runtime/vision";
|
|
9437
|
+
|
|
8685
9438
|
// src/server/autophase-routes.ts
|
|
8686
9439
|
async function handleAutoPhaseRoute(_ws, msg, handlers) {
|
|
8687
9440
|
if (!msg.type.startsWith("autophase.")) return false;
|
|
@@ -8701,14 +9454,23 @@ async function handleBrainRoute(ws, msg, handlers) {
|
|
|
8701
9454
|
case "brain.ask":
|
|
8702
9455
|
await handlers.ask(ws, msg);
|
|
8703
9456
|
return true;
|
|
9457
|
+
case "brain.config.get":
|
|
9458
|
+
await handlers.configGet(ws, msg);
|
|
9459
|
+
return true;
|
|
9460
|
+
case "brain.config.set":
|
|
9461
|
+
await handlers.configSet(ws, msg);
|
|
9462
|
+
return true;
|
|
8704
9463
|
default:
|
|
8705
9464
|
return false;
|
|
8706
9465
|
}
|
|
8707
9466
|
}
|
|
9467
|
+
|
|
9468
|
+
// src/server/goal-handlers.ts
|
|
9469
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
8708
9470
|
async function handleGoalGet(projectRoot, broadcast2) {
|
|
8709
9471
|
try {
|
|
8710
|
-
const goalPath = resolveWstackPaths
|
|
8711
|
-
const { readFile: readFile10 } = await import(
|
|
9472
|
+
const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
|
|
9473
|
+
const { readFile: readFile10 } = await import("node:fs/promises");
|
|
8712
9474
|
const raw = await readFile10(goalPath, "utf8");
|
|
8713
9475
|
const goal = JSON.parse(raw);
|
|
8714
9476
|
broadcast2({ type: "goal.updated", payload: goal });
|
|
@@ -8762,7 +9524,7 @@ async function handleTasksGet(ctx, ws) {
|
|
|
8762
9524
|
const taskPath = ctx.context.meta["task.path"];
|
|
8763
9525
|
if (typeof taskPath === "string" && taskPath) {
|
|
8764
9526
|
try {
|
|
8765
|
-
const { loadTasks } = await import(
|
|
9527
|
+
const { loadTasks } = await import("@wrongstack/core");
|
|
8766
9528
|
const file = await loadTasks(taskPath);
|
|
8767
9529
|
ctx.send(ws, { type: "tasks.updated", payload: sessionPayload(ctx, { tasks: file?.tasks ?? [] }) });
|
|
8768
9530
|
} catch {
|
|
@@ -8782,7 +9544,7 @@ async function handleTaskUpdate(ctx, ws, payload) {
|
|
|
8782
9544
|
return;
|
|
8783
9545
|
}
|
|
8784
9546
|
try {
|
|
8785
|
-
const { loadTasks, saveTasks } = await import(
|
|
9547
|
+
const { loadTasks, saveTasks } = await import("@wrongstack/core");
|
|
8786
9548
|
const file = await loadTasks(taskPath);
|
|
8787
9549
|
if (!file) {
|
|
8788
9550
|
sendResult2(ws, ctx, false, "No task file found.");
|
|
@@ -8811,7 +9573,7 @@ async function handlePlanGet(ctx, ws) {
|
|
|
8811
9573
|
const sessionId = ctx.context.session?.id ?? "";
|
|
8812
9574
|
if (typeof planPath === "string" && planPath) {
|
|
8813
9575
|
try {
|
|
8814
|
-
const { loadPlan } = await import(
|
|
9576
|
+
const { loadPlan } = await import("@wrongstack/core");
|
|
8815
9577
|
const plan = await loadPlan(planPath);
|
|
8816
9578
|
ctx.send(ws, {
|
|
8817
9579
|
type: "plan.updated",
|
|
@@ -8852,7 +9614,7 @@ async function handlePlanTemplateUse(ctx, ws, template) {
|
|
|
8852
9614
|
return;
|
|
8853
9615
|
}
|
|
8854
9616
|
try {
|
|
8855
|
-
const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import(
|
|
9617
|
+
const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import("@wrongstack/core");
|
|
8856
9618
|
const tpl = getPlanTemplate(template);
|
|
8857
9619
|
if (!tpl) {
|
|
8858
9620
|
sendResult2(ws, ctx, false, `Unknown template "${template}".`);
|
|
@@ -8877,7 +9639,7 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
|
8877
9639
|
return;
|
|
8878
9640
|
}
|
|
8879
9641
|
try {
|
|
8880
|
-
const { mutatePlan, setPlanItemStatus } = await import(
|
|
9642
|
+
const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
|
|
8881
9643
|
let changed = false;
|
|
8882
9644
|
const plan = await mutatePlan(planPath, sessionId, async (p) => {
|
|
8883
9645
|
const before = p.updatedAt;
|
|
@@ -8944,6 +9706,45 @@ async function handleWorklistMessage(ctx, ws, msg) {
|
|
|
8944
9706
|
return;
|
|
8945
9707
|
}
|
|
8946
9708
|
}
|
|
9709
|
+
|
|
9710
|
+
// src/server/kanban-routes.ts
|
|
9711
|
+
import {
|
|
9712
|
+
deserializeTaskGraph,
|
|
9713
|
+
serializeTaskGraph
|
|
9714
|
+
} from "@wrongstack/core";
|
|
9715
|
+
import {
|
|
9716
|
+
addColumn,
|
|
9717
|
+
addGoalMetricToTask,
|
|
9718
|
+
addTask,
|
|
9719
|
+
assignTask,
|
|
9720
|
+
claimReadyTask,
|
|
9721
|
+
copyTaskToBoard,
|
|
9722
|
+
createBoard,
|
|
9723
|
+
duplicateBoard,
|
|
9724
|
+
exportBoardToTaskGraph,
|
|
9725
|
+
generateBoardFromDescription,
|
|
9726
|
+
getBoard,
|
|
9727
|
+
getKanbanOrchestrationSnapshot,
|
|
9728
|
+
getKanbanQueueHealth,
|
|
9729
|
+
getTask,
|
|
9730
|
+
getTaskChain,
|
|
9731
|
+
listReadyTasks,
|
|
9732
|
+
listBoards,
|
|
9733
|
+
mergeTasks,
|
|
9734
|
+
moveTask,
|
|
9735
|
+
parseLinesIntoTasks,
|
|
9736
|
+
removeBoard,
|
|
9737
|
+
removeColumn,
|
|
9738
|
+
removeTask,
|
|
9739
|
+
releaseTaskClaim,
|
|
9740
|
+
setTaskChain,
|
|
9741
|
+
splitTask,
|
|
9742
|
+
syncBoardFromTaskGraph,
|
|
9743
|
+
transferTaskToBoard,
|
|
9744
|
+
updateBoard,
|
|
9745
|
+
updateGoalMetricOnTask,
|
|
9746
|
+
updateTask
|
|
9747
|
+
} from "@wrongstack/kanban";
|
|
8947
9748
|
function ok(ws, type, data) {
|
|
8948
9749
|
send(ws, { type, payload: { success: true, data: data ?? null } });
|
|
8949
9750
|
}
|
|
@@ -9389,6 +10190,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9389
10190
|
"Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
|
|
9390
10191
|
);
|
|
9391
10192
|
return true;
|
|
10193
|
+
case "kanban.capabilities":
|
|
10194
|
+
ok(ws, type, { dispatchSupported: false });
|
|
10195
|
+
return true;
|
|
9392
10196
|
case "kanban.task.remove": {
|
|
9393
10197
|
const boardId = payload?.boardId;
|
|
9394
10198
|
const taskId = payload?.taskId;
|
|
@@ -9468,6 +10272,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
|
|
|
9468
10272
|
case "mailbox.purge":
|
|
9469
10273
|
await handlers.purge(ws, msg);
|
|
9470
10274
|
return true;
|
|
10275
|
+
case "mailbox.compact":
|
|
10276
|
+
await handlers.compact(ws, msg);
|
|
10277
|
+
return true;
|
|
9471
10278
|
default:
|
|
9472
10279
|
return false;
|
|
9473
10280
|
}
|
|
@@ -9506,6 +10313,18 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
9506
10313
|
case "mcp.discover":
|
|
9507
10314
|
await handlers.discover(ws, msg);
|
|
9508
10315
|
return true;
|
|
10316
|
+
case "mcp.resources":
|
|
10317
|
+
await handlers.resources(ws, msg);
|
|
10318
|
+
return true;
|
|
10319
|
+
case "mcp.prompts":
|
|
10320
|
+
await handlers.prompts(ws, msg);
|
|
10321
|
+
return true;
|
|
10322
|
+
case "mcp.resource.read":
|
|
10323
|
+
await handlers.resourceRead(ws, msg);
|
|
10324
|
+
return true;
|
|
10325
|
+
case "mcp.prompt.get":
|
|
10326
|
+
await handlers.promptGet(ws, msg);
|
|
10327
|
+
return true;
|
|
9509
10328
|
default:
|
|
9510
10329
|
return false;
|
|
9511
10330
|
}
|
|
@@ -9544,7 +10363,7 @@ async function handlePrefsRoute(ws, msg, handlers) {
|
|
|
9544
10363
|
// src/server/process-handlers.ts
|
|
9545
10364
|
async function handleProcessList(ws) {
|
|
9546
10365
|
try {
|
|
9547
|
-
const { getProcessRegistry } = await import(
|
|
10366
|
+
const { getProcessRegistry } = await import("@wrongstack/tools");
|
|
9548
10367
|
const procs = getProcessRegistry().list();
|
|
9549
10368
|
send(ws, {
|
|
9550
10369
|
type: "process.list",
|
|
@@ -9571,7 +10390,7 @@ async function handleProcessKill(ws, payload) {
|
|
|
9571
10390
|
}
|
|
9572
10391
|
const { pid } = parsed.value;
|
|
9573
10392
|
try {
|
|
9574
|
-
const { getProcessRegistry } = await import(
|
|
10393
|
+
const { getProcessRegistry } = await import("@wrongstack/tools");
|
|
9575
10394
|
const proc = getProcessRegistry().get(pid);
|
|
9576
10395
|
if (proc?.protected) {
|
|
9577
10396
|
sendResult(ws, false, `Cannot kill protected process (PID ${pid})`);
|
|
@@ -9585,7 +10404,7 @@ async function handleProcessKill(ws, payload) {
|
|
|
9585
10404
|
}
|
|
9586
10405
|
async function handleProcessKillAll(ws) {
|
|
9587
10406
|
try {
|
|
9588
|
-
const { getProcessRegistry } = await import(
|
|
10407
|
+
const { getProcessRegistry } = await import("@wrongstack/tools");
|
|
9589
10408
|
getProcessRegistry().killAll();
|
|
9590
10409
|
sendResult(ws, true, "All processes killed");
|
|
9591
10410
|
} catch (err) {
|
|
@@ -9895,7 +10714,7 @@ function createMessageDispatcher(opts) {
|
|
|
9895
10714
|
skillLoader: deps2.skillLoader,
|
|
9896
10715
|
skillInstaller: deps2.skillInstaller,
|
|
9897
10716
|
projectRoot,
|
|
9898
|
-
projectSkillsDir:
|
|
10717
|
+
projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
|
|
9899
10718
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
9900
10719
|
};
|
|
9901
10720
|
}
|
|
@@ -9968,7 +10787,8 @@ function createMessageDispatcher(opts) {
|
|
|
9968
10787
|
}
|
|
9969
10788
|
case "user_message": {
|
|
9970
10789
|
if (!ensureCurrentSession(ws, msg, "user_message")) return;
|
|
9971
|
-
const
|
|
10790
|
+
const userPayload = msg.payload;
|
|
10791
|
+
const content = userPayload.content;
|
|
9972
10792
|
if (runLock.get()) {
|
|
9973
10793
|
send(ws, {
|
|
9974
10794
|
type: "error",
|
|
@@ -9982,8 +10802,24 @@ function createMessageDispatcher(opts) {
|
|
|
9982
10802
|
const thisRun = new AbortController();
|
|
9983
10803
|
runLock.set(thisRun);
|
|
9984
10804
|
try {
|
|
10805
|
+
let input = content;
|
|
10806
|
+
const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
|
|
10807
|
+
if (imageBlocks.length > 0) {
|
|
10808
|
+
const routed = await routeImagesForModel(
|
|
10809
|
+
buildUserContentBlocks(content, imageBlocks),
|
|
10810
|
+
{
|
|
10811
|
+
supportsVision: deps2.agent.ctx.provider.capabilities.vision,
|
|
10812
|
+
adapters: () => createToolVisionAdapters(deps2.agent.tools),
|
|
10813
|
+
ctx: deps2.agent.ctx,
|
|
10814
|
+
signal: thisRun.signal,
|
|
10815
|
+
providerId: deps2.agent.ctx.provider.id,
|
|
10816
|
+
model: deps2.agent.ctx.model
|
|
10817
|
+
}
|
|
10818
|
+
);
|
|
10819
|
+
input = routed.blocks;
|
|
10820
|
+
}
|
|
9985
10821
|
const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
|
|
9986
|
-
const result = await deps2.agent.run(
|
|
10822
|
+
const result = await deps2.agent.run(input, {
|
|
9987
10823
|
signal: thisRun.signal,
|
|
9988
10824
|
maxIterations: maxIt
|
|
9989
10825
|
});
|
|
@@ -10001,13 +10837,24 @@ function createMessageDispatcher(opts) {
|
|
|
10001
10837
|
})
|
|
10002
10838
|
});
|
|
10003
10839
|
} catch (err) {
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10840
|
+
if (err instanceof IncomingImageError || err instanceof ImageInputUnsupportedError || err instanceof VisionUrlBlockedError) {
|
|
10841
|
+
send(ws, {
|
|
10842
|
+
type: "error",
|
|
10843
|
+
payload: sessionPayload2({
|
|
10844
|
+
phase: "user_message",
|
|
10845
|
+
...err instanceof ImageInputUnsupportedError ? { code: "vision_unsupported" } : {},
|
|
10846
|
+
message: err.message
|
|
10847
|
+
})
|
|
10848
|
+
});
|
|
10849
|
+
} else {
|
|
10850
|
+
send(ws, {
|
|
10851
|
+
type: "error",
|
|
10852
|
+
payload: sessionPayload2({
|
|
10853
|
+
phase: "agent.run",
|
|
10854
|
+
message: errMessage(err)
|
|
10855
|
+
})
|
|
10856
|
+
});
|
|
10857
|
+
}
|
|
10011
10858
|
} finally {
|
|
10012
10859
|
if (runLock.get() === thisRun) {
|
|
10013
10860
|
runLock.set(null);
|
|
@@ -10055,7 +10902,7 @@ function createMessageDispatcher(opts) {
|
|
|
10055
10902
|
break;
|
|
10056
10903
|
}
|
|
10057
10904
|
case "tool.disable": {
|
|
10058
|
-
const name2 =
|
|
10905
|
+
const name2 = msg.payload?.name;
|
|
10059
10906
|
if (!name2) {
|
|
10060
10907
|
send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
|
|
10061
10908
|
break;
|
|
@@ -10070,7 +10917,7 @@ function createMessageDispatcher(opts) {
|
|
|
10070
10917
|
break;
|
|
10071
10918
|
}
|
|
10072
10919
|
case "tool.enable": {
|
|
10073
|
-
const name2 =
|
|
10920
|
+
const name2 = msg.payload?.name;
|
|
10074
10921
|
if (!name2) {
|
|
10075
10922
|
send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
|
|
10076
10923
|
break;
|
|
@@ -10114,6 +10961,14 @@ function createMessageDispatcher(opts) {
|
|
|
10114
10961
|
throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
|
|
10115
10962
|
case "mcp.discover":
|
|
10116
10963
|
throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
|
|
10964
|
+
case "mcp.resources":
|
|
10965
|
+
throw new Error("handleMcpRoute did not claim mcp.resources \u2014 check chain order");
|
|
10966
|
+
case "mcp.prompts":
|
|
10967
|
+
throw new Error("handleMcpRoute did not claim mcp.prompts \u2014 check chain order");
|
|
10968
|
+
case "mcp.resource.read":
|
|
10969
|
+
throw new Error("handleMcpRoute did not claim mcp.resource.read \u2014 check chain order");
|
|
10970
|
+
case "mcp.prompt.get":
|
|
10971
|
+
throw new Error("handleMcpRoute did not claim mcp.prompt.get \u2014 check chain order");
|
|
10117
10972
|
// Skills — full request→response cycle lives in skills-handlers.ts.
|
|
10118
10973
|
case "skills.list":
|
|
10119
10974
|
await handleSkillsList(ws, makeSkillsContext());
|
|
@@ -10350,6 +11205,7 @@ function createMessageDispatcher(opts) {
|
|
|
10350
11205
|
break;
|
|
10351
11206
|
}
|
|
10352
11207
|
case "prefs.update": {
|
|
11208
|
+
void ws;
|
|
10353
11209
|
throw new Error("handlePrefsRoute did not claim prefs.update \u2014 check chain order");
|
|
10354
11210
|
}
|
|
10355
11211
|
case "prefs.get": {
|
|
@@ -10363,6 +11219,11 @@ function createMessageDispatcher(opts) {
|
|
|
10363
11219
|
}
|
|
10364
11220
|
};
|
|
10365
11221
|
}
|
|
11222
|
+
|
|
11223
|
+
// src/server/pref-helpers.ts
|
|
11224
|
+
import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
|
|
11225
|
+
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
11226
|
+
import * as fs13 from "node:fs/promises";
|
|
10366
11227
|
var PREF_KEYS = [
|
|
10367
11228
|
"autonomy",
|
|
10368
11229
|
"autonomyDelayMs",
|
|
@@ -10408,7 +11269,19 @@ var PREF_KEYS = [
|
|
|
10408
11269
|
"favoriteModels",
|
|
10409
11270
|
"favoriteModelsOnly",
|
|
10410
11271
|
"modelMatrix",
|
|
10411
|
-
"fallbackAuto"
|
|
11272
|
+
"fallbackAuto",
|
|
11273
|
+
// Refiner + TUI visual prefs (parity with the CLI's embedded server —
|
|
11274
|
+
// these were browser-editable there but rejected as unknown keys here).
|
|
11275
|
+
"refinerProvider",
|
|
11276
|
+
"refinerModel",
|
|
11277
|
+
"thinkingWord",
|
|
11278
|
+
"statuslineMode",
|
|
11279
|
+
"animationStyle",
|
|
11280
|
+
// Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
|
|
11281
|
+
"breakerEnabled",
|
|
11282
|
+
"breakerAutoKillResetMs",
|
|
11283
|
+
"fsAccess",
|
|
11284
|
+
"debugStream"
|
|
10412
11285
|
];
|
|
10413
11286
|
function prefSnapshot(contextMeta) {
|
|
10414
11287
|
const snapshot = {};
|
|
@@ -10422,7 +11295,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
|
|
|
10422
11295
|
const write = async () => {
|
|
10423
11296
|
let raw;
|
|
10424
11297
|
try {
|
|
10425
|
-
raw = await
|
|
11298
|
+
raw = await fs13.readFile(globalConfigPath, "utf8");
|
|
10426
11299
|
} catch {
|
|
10427
11300
|
raw = "{}";
|
|
10428
11301
|
}
|
|
@@ -10433,10 +11306,10 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
|
|
|
10433
11306
|
logger.warn(`${errorLabel}: refusing to overwrite corrupt config at ${globalConfigPath}`);
|
|
10434
11307
|
return;
|
|
10435
11308
|
}
|
|
10436
|
-
const decrypted =
|
|
11309
|
+
const decrypted = decryptConfigSecrets2(parsed, vault);
|
|
10437
11310
|
mutate(decrypted);
|
|
10438
|
-
const encrypted =
|
|
10439
|
-
await
|
|
11311
|
+
const encrypted = encryptConfigSecrets2(decrypted, vault);
|
|
11312
|
+
await atomicWrite6(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
10440
11313
|
};
|
|
10441
11314
|
const next = holder.lock.then(write);
|
|
10442
11315
|
holder.lock = next.then(
|
|
@@ -10479,6 +11352,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
10479
11352
|
setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
|
|
10480
11353
|
if (typeof payload["enhanceLanguage"] === "string")
|
|
10481
11354
|
setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
|
|
11355
|
+
if (typeof payload["refinerProvider"] === "string")
|
|
11356
|
+
setAutonomy("refinerProvider", payload["refinerProvider"]);
|
|
11357
|
+
if (typeof payload["refinerModel"] === "string")
|
|
11358
|
+
setAutonomy("refinerModel", payload["refinerModel"]);
|
|
11359
|
+
if (typeof payload["thinkingWord"] === "string")
|
|
11360
|
+
setAutonomy("thinkingWord", payload["thinkingWord"]);
|
|
11361
|
+
if (typeof payload["statuslineMode"] === "string")
|
|
11362
|
+
setAutonomy("statuslineMode", payload["statuslineMode"]);
|
|
11363
|
+
if (typeof payload["animationStyle"] === "string")
|
|
11364
|
+
setAutonomy("animationStyle", payload["animationStyle"]);
|
|
10482
11365
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
10483
11366
|
if (typeof payload["nextPrediction"] === "boolean")
|
|
10484
11367
|
decrypted.nextPrediction = payload["nextPrediction"];
|
|
@@ -10596,15 +11479,38 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
10596
11479
|
}
|
|
10597
11480
|
decrypted.modelRuntime = mr;
|
|
10598
11481
|
}
|
|
11482
|
+
if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
|
|
11483
|
+
const cb = decrypted.circuitBreaker ?? {};
|
|
11484
|
+
if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
|
|
11485
|
+
if (typeof payload["breakerAutoKillResetMs"] === "number")
|
|
11486
|
+
cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
|
|
11487
|
+
decrypted.circuitBreaker = cb;
|
|
11488
|
+
}
|
|
11489
|
+
if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
|
|
11490
|
+
const restrict = payload["fsAccess"] === "project";
|
|
11491
|
+
const toolsCfg = decrypted.tools ?? {};
|
|
11492
|
+
toolsCfg.restrictToProjectRoot = restrict;
|
|
11493
|
+
decrypted.tools = toolsCfg;
|
|
11494
|
+
const featsCfg = decrypted.features ?? {};
|
|
11495
|
+
featsCfg.allowOutsideProjectRoot = !restrict;
|
|
11496
|
+
decrypted.features = featsCfg;
|
|
11497
|
+
}
|
|
11498
|
+
if (typeof payload["debugStream"] === "boolean")
|
|
11499
|
+
decrypted.debugStream = payload["debugStream"];
|
|
10599
11500
|
}, "prefs");
|
|
10600
11501
|
}
|
|
11502
|
+
|
|
11503
|
+
// src/server/projects-manifest.ts
|
|
11504
|
+
import * as fs14 from "node:fs/promises";
|
|
11505
|
+
import * as path18 from "node:path";
|
|
11506
|
+
import { projectSlug } from "@wrongstack/core";
|
|
10601
11507
|
function projectsJsonPath(globalConfigPath) {
|
|
10602
|
-
const base =
|
|
10603
|
-
return
|
|
11508
|
+
const base = path18.dirname(globalConfigPath);
|
|
11509
|
+
return path18.join(base, "projects.json");
|
|
10604
11510
|
}
|
|
10605
11511
|
async function loadManifest(globalConfigPath) {
|
|
10606
11512
|
try {
|
|
10607
|
-
const raw = await
|
|
11513
|
+
const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
10608
11514
|
const parsed = JSON.parse(raw);
|
|
10609
11515
|
return { projects: parsed.projects ?? [] };
|
|
10610
11516
|
} catch {
|
|
@@ -10613,18 +11519,26 @@ async function loadManifest(globalConfigPath) {
|
|
|
10613
11519
|
}
|
|
10614
11520
|
async function saveManifest(manifest, globalConfigPath) {
|
|
10615
11521
|
const file = projectsJsonPath(globalConfigPath);
|
|
10616
|
-
await
|
|
10617
|
-
await
|
|
11522
|
+
await fs14.mkdir(path18.dirname(file), { recursive: true });
|
|
11523
|
+
await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
10618
11524
|
}
|
|
10619
11525
|
function generateProjectSlug(rootPath) {
|
|
10620
11526
|
return projectSlug(rootPath);
|
|
10621
11527
|
}
|
|
10622
11528
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
10623
|
-
const base =
|
|
10624
|
-
const dir =
|
|
10625
|
-
await
|
|
11529
|
+
const base = path18.dirname(globalConfigPath);
|
|
11530
|
+
const dir = path18.join(base, "projects", slug);
|
|
11531
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
10626
11532
|
return dir;
|
|
10627
11533
|
}
|
|
11534
|
+
|
|
11535
|
+
// src/server/provider-handlers.ts
|
|
11536
|
+
import { DefaultSecretScrubber } from "@wrongstack/core";
|
|
11537
|
+
import {
|
|
11538
|
+
beginOAuthLogin
|
|
11539
|
+
} from "@wrongstack/providers/oauth";
|
|
11540
|
+
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
11541
|
+
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
10628
11542
|
function projectSavedProviders(providers) {
|
|
10629
11543
|
return Object.entries(providers).map(([id, cfg]) => {
|
|
10630
11544
|
const keys = normalizeKeys(cfg);
|
|
@@ -10655,7 +11569,7 @@ function createProviderHandlers(deps2) {
|
|
|
10655
11569
|
}
|
|
10656
11570
|
async function saveConfigProviders(providers) {
|
|
10657
11571
|
const next = configWriteLock.then(() => saveProviders(globalConfigPath, vault, providers)).catch((err) => {
|
|
10658
|
-
const msg =
|
|
11572
|
+
const msg = toErrorMessage9(err);
|
|
10659
11573
|
console.error(JSON.stringify({
|
|
10660
11574
|
level: "error",
|
|
10661
11575
|
event: "webui.provider_save_failed",
|
|
@@ -10927,10 +11841,23 @@ function createProviderHandlers(deps2) {
|
|
|
10927
11841
|
loadConfigProviders
|
|
10928
11842
|
};
|
|
10929
11843
|
}
|
|
11844
|
+
|
|
11845
|
+
// src/server/routes.ts
|
|
11846
|
+
import path20 from "node:path";
|
|
11847
|
+
import {
|
|
11848
|
+
enhanceUserPrompt,
|
|
11849
|
+
gatedEnhancerReasoning,
|
|
11850
|
+
recentTextTurns,
|
|
11851
|
+
resolveProviderModelList
|
|
11852
|
+
} from "@wrongstack/core";
|
|
11853
|
+
import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
|
|
11854
|
+
|
|
11855
|
+
// src/server/mailbox-handlers.ts
|
|
11856
|
+
import { GlobalMailbox as GlobalMailbox3, resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core";
|
|
10930
11857
|
async function handleMailboxMessages(ws, deps2, payload) {
|
|
10931
11858
|
try {
|
|
10932
|
-
const dir =
|
|
10933
|
-
const mb = new
|
|
11859
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
11860
|
+
const mb = new GlobalMailbox3(dir);
|
|
10934
11861
|
const limit = payload?.limit ?? 30;
|
|
10935
11862
|
const unreadForAgent = payload?.unreadOnly === true && payload.agentId !== void 0;
|
|
10936
11863
|
const messages = await mb.query({
|
|
@@ -10970,8 +11897,8 @@ async function handleMailboxMessages(ws, deps2, payload) {
|
|
|
10970
11897
|
}
|
|
10971
11898
|
async function handleMailboxAgents(ws, deps2, payload) {
|
|
10972
11899
|
try {
|
|
10973
|
-
const dir =
|
|
10974
|
-
const mb = new
|
|
11900
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
11901
|
+
const mb = new GlobalMailbox3(dir);
|
|
10975
11902
|
const agents = payload?.onlineOnly ? await mb.getOnlineAgents() : await mb.getAgentStatuses();
|
|
10976
11903
|
send(ws, {
|
|
10977
11904
|
type: "mailbox.agents",
|
|
@@ -10999,8 +11926,8 @@ async function handleMailboxAgents(ws, deps2, payload) {
|
|
|
10999
11926
|
}
|
|
11000
11927
|
async function handleMailboxClear(ws, deps2) {
|
|
11001
11928
|
try {
|
|
11002
|
-
const dir =
|
|
11003
|
-
const mb = new
|
|
11929
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
11930
|
+
const mb = new GlobalMailbox3(dir);
|
|
11004
11931
|
await mb.clearAll();
|
|
11005
11932
|
send(ws, { type: "mailbox.cleared", payload: {} });
|
|
11006
11933
|
} catch (err) {
|
|
@@ -11009,14 +11936,31 @@ async function handleMailboxClear(ws, deps2) {
|
|
|
11009
11936
|
}
|
|
11010
11937
|
async function handleMailboxPurge(ws, deps2, opts) {
|
|
11011
11938
|
try {
|
|
11012
|
-
const dir =
|
|
11013
|
-
const mb = new
|
|
11939
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
11940
|
+
const mb = new GlobalMailbox3(dir);
|
|
11014
11941
|
const result = await mb.purgeStale(opts);
|
|
11015
11942
|
send(ws, { type: "mailbox.purged", payload: result });
|
|
11016
11943
|
} catch (err) {
|
|
11017
11944
|
send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
|
|
11018
11945
|
}
|
|
11019
11946
|
}
|
|
11947
|
+
async function handleMailboxCompact(ws, deps2, opts) {
|
|
11948
|
+
try {
|
|
11949
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
11950
|
+
const mb = new GlobalMailbox3(dir);
|
|
11951
|
+
const result = await mb.autoCompact(opts);
|
|
11952
|
+
send(ws, { type: "mailbox.compacted", payload: result });
|
|
11953
|
+
} catch (err) {
|
|
11954
|
+
send(ws, { type: "mailbox.compacted", payload: { error: errMessage(err) } });
|
|
11955
|
+
}
|
|
11956
|
+
}
|
|
11957
|
+
|
|
11958
|
+
// src/server/mode-handlers.ts
|
|
11959
|
+
import {
|
|
11960
|
+
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
|
|
11961
|
+
resolveWstackPaths as resolveWstackPaths2,
|
|
11962
|
+
ToolValidationError as ToolValidationError5
|
|
11963
|
+
} from "@wrongstack/core";
|
|
11020
11964
|
function createModeHandlers(ctx) {
|
|
11021
11965
|
return {
|
|
11022
11966
|
listModes: async (ws) => {
|
|
@@ -11059,14 +12003,14 @@ function createModeHandlers(ctx) {
|
|
|
11059
12003
|
} else {
|
|
11060
12004
|
const found = await ctx.modeStore.getMode(id);
|
|
11061
12005
|
if (!found) {
|
|
11062
|
-
throw new
|
|
12006
|
+
throw new ToolValidationError5({ message: `Unknown mode "${id}"`, field: "modeId" });
|
|
11063
12007
|
}
|
|
11064
12008
|
await ctx.modeStore.setActiveMode(id);
|
|
11065
12009
|
}
|
|
11066
12010
|
ctx.setModeId(id);
|
|
11067
12011
|
const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
|
|
11068
|
-
const paths =
|
|
11069
|
-
const freshBuilder = new
|
|
12012
|
+
const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
12013
|
+
const freshBuilder = new DefaultSystemPromptBuilder2({
|
|
11070
12014
|
memoryStore: ctx.memoryStore,
|
|
11071
12015
|
skillLoader: ctx.skillLoader,
|
|
11072
12016
|
modeStore: ctx.modeStore,
|
|
@@ -11096,6 +12040,9 @@ function createModeHandlers(ctx) {
|
|
|
11096
12040
|
}
|
|
11097
12041
|
};
|
|
11098
12042
|
}
|
|
12043
|
+
|
|
12044
|
+
// src/server/project-handlers.ts
|
|
12045
|
+
import * as path19 from "node:path";
|
|
11099
12046
|
function createProjectHandlers(ctx) {
|
|
11100
12047
|
return {
|
|
11101
12048
|
listProjects: async (ws) => {
|
|
@@ -11121,7 +12068,7 @@ function createProjectHandlers(ctx) {
|
|
|
11121
12068
|
selectProject: async (ws, msg) => {
|
|
11122
12069
|
const payload = msg.payload;
|
|
11123
12070
|
const root = typeof payload?.root === "string" ? payload.root : "";
|
|
11124
|
-
const name2 = typeof payload?.name === "string" ? payload.name : root ?
|
|
12071
|
+
const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
|
|
11125
12072
|
send(ws, {
|
|
11126
12073
|
type: "projects.selected",
|
|
11127
12074
|
payload: {
|
|
@@ -11154,6 +12101,14 @@ function createProjectHandlers(ctx) {
|
|
|
11154
12101
|
}
|
|
11155
12102
|
};
|
|
11156
12103
|
}
|
|
12104
|
+
|
|
12105
|
+
// src/server/session-handlers.ts
|
|
12106
|
+
import {
|
|
12107
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
12108
|
+
repairToolUseAdjacency,
|
|
12109
|
+
resolveContextWindowPolicy as resolveContextWindowPolicy3
|
|
12110
|
+
} from "@wrongstack/core";
|
|
12111
|
+
import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
|
|
11157
12112
|
function createSessionHandlers(ctx) {
|
|
11158
12113
|
const currentSessionId = () => ctx.getSession().id;
|
|
11159
12114
|
const sessionPayload2 = (payload) => {
|
|
@@ -11179,33 +12134,46 @@ function createSessionHandlers(ctx) {
|
|
|
11179
12134
|
});
|
|
11180
12135
|
return false;
|
|
11181
12136
|
};
|
|
12137
|
+
const finalizeSession = async (writer) => {
|
|
12138
|
+
await writer.append({
|
|
12139
|
+
type: "session_end",
|
|
12140
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12141
|
+
usage: ctx.tokenCounter.total()
|
|
12142
|
+
}).catch(() => void 0);
|
|
12143
|
+
await writer.close().catch(() => void 0);
|
|
12144
|
+
};
|
|
12145
|
+
const activateSession = async (next, messages, usage) => {
|
|
12146
|
+
const current = ctx.getSession();
|
|
12147
|
+
if (current !== next) await finalizeSession(current);
|
|
12148
|
+
ctx.setSession(next);
|
|
12149
|
+
ctx.context.session = next;
|
|
12150
|
+
ctx.context.state.replaceMessages(messages);
|
|
12151
|
+
ctx.context.state.replaceTodos([]);
|
|
12152
|
+
ctx.context.readFiles.clear();
|
|
12153
|
+
ctx.context.fileMtimes.clear();
|
|
12154
|
+
ctx.context.state.setMeta(
|
|
12155
|
+
"plan.path",
|
|
12156
|
+
sessionScopedPath2(ctx.sessionsDir, next.id, ".plan.json")
|
|
12157
|
+
);
|
|
12158
|
+
ctx.context.state.setMeta(
|
|
12159
|
+
"task.path",
|
|
12160
|
+
sessionScopedPath2(ctx.sessionsDir, next.id, ".tasks.json")
|
|
12161
|
+
);
|
|
12162
|
+
ctx.tokenCounter.reset();
|
|
12163
|
+
if (usage) ctx.tokenCounter.account(usage, ctx.config.model);
|
|
12164
|
+
ctx.setSessionStartedAt(Date.now());
|
|
12165
|
+
await ctx.onSessionSwapped(next.id);
|
|
12166
|
+
};
|
|
11182
12167
|
return {
|
|
11183
12168
|
newSession: async (ws, msg) => {
|
|
11184
12169
|
if (!ensureCurrentSession(ws, msg, "session.new")) return;
|
|
11185
|
-
const session = ctx.getSession();
|
|
11186
|
-
try {
|
|
11187
|
-
await session.append({
|
|
11188
|
-
type: "session_end",
|
|
11189
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11190
|
-
usage: ctx.tokenCounter.total()
|
|
11191
|
-
});
|
|
11192
|
-
await session.close();
|
|
11193
|
-
} catch {
|
|
11194
|
-
}
|
|
11195
12170
|
const next = await ctx.getSessionStore().create({
|
|
11196
12171
|
id: "",
|
|
11197
12172
|
title: "",
|
|
11198
12173
|
model: ctx.config.model,
|
|
11199
12174
|
provider: ctx.config.provider
|
|
11200
12175
|
});
|
|
11201
|
-
|
|
11202
|
-
ctx.context.session = next;
|
|
11203
|
-
ctx.context.state.replaceMessages([]);
|
|
11204
|
-
ctx.context.state.replaceTodos([]);
|
|
11205
|
-
ctx.context.readFiles.clear();
|
|
11206
|
-
ctx.context.fileMtimes.clear();
|
|
11207
|
-
ctx.tokenCounter.reset();
|
|
11208
|
-
ctx.setSessionStartedAt(Date.now());
|
|
12176
|
+
await activateSession(next, []);
|
|
11209
12177
|
broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
|
|
11210
12178
|
},
|
|
11211
12179
|
clearContext: async (ws, msg) => {
|
|
@@ -11307,7 +12275,7 @@ function createSessionHandlers(ctx) {
|
|
|
11307
12275
|
return;
|
|
11308
12276
|
}
|
|
11309
12277
|
const { id } = parsed.value;
|
|
11310
|
-
let policy =
|
|
12278
|
+
let policy = resolveContextWindowPolicy3({}, id);
|
|
11311
12279
|
if (policy.id !== id) {
|
|
11312
12280
|
const customModes = ctx.customModeStore.list().filter((m) => m.custom === true);
|
|
11313
12281
|
const custom = customModes.find((m) => m.id === id);
|
|
@@ -11379,7 +12347,7 @@ function createSessionHandlers(ctx) {
|
|
|
11379
12347
|
const { id } = parsed.value;
|
|
11380
12348
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
11381
12349
|
ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
11382
|
-
ctx.context.meta["contextWindowPolicy"] =
|
|
12350
|
+
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
|
|
11383
12351
|
}
|
|
11384
12352
|
const result = ctx.customModeStore.remove(id);
|
|
11385
12353
|
sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
|
|
@@ -11461,23 +12429,7 @@ function createSessionHandlers(ctx) {
|
|
|
11461
12429
|
return;
|
|
11462
12430
|
}
|
|
11463
12431
|
const resumed = await ctx.getSessionStore().resume(id);
|
|
11464
|
-
|
|
11465
|
-
await current.append({
|
|
11466
|
-
type: "session_end",
|
|
11467
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11468
|
-
usage: ctx.tokenCounter.total()
|
|
11469
|
-
});
|
|
11470
|
-
await current.close();
|
|
11471
|
-
} catch {
|
|
11472
|
-
}
|
|
11473
|
-
ctx.setSession(resumed.writer);
|
|
11474
|
-
ctx.context.session = resumed.writer;
|
|
11475
|
-
ctx.context.state.replaceMessages(resumed.data.messages);
|
|
11476
|
-
ctx.context.readFiles.clear();
|
|
11477
|
-
ctx.context.fileMtimes.clear();
|
|
11478
|
-
ctx.tokenCounter.reset();
|
|
11479
|
-
ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
|
|
11480
|
-
ctx.setSessionStartedAt(Date.now());
|
|
12432
|
+
await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
|
|
11481
12433
|
broadcast(ctx.clients, {
|
|
11482
12434
|
type: "session.start",
|
|
11483
12435
|
payload: {
|
|
@@ -11499,12 +12451,9 @@ function createSessionHandlers(ctx) {
|
|
|
11499
12451
|
listCheckpoints: async (ws, msg) => {
|
|
11500
12452
|
if (!ensureCurrentSession(ws, msg, "session.checkpoints")) return;
|
|
11501
12453
|
try {
|
|
11502
|
-
const { DefaultSessionRewinder } = await import(
|
|
12454
|
+
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
11503
12455
|
const projectRoot = ctx.getProjectRoot();
|
|
11504
|
-
const rewinder = new DefaultSessionRewinder(
|
|
11505
|
-
path5.join(projectRoot, ".wrongstack", "sessions"),
|
|
11506
|
-
projectRoot
|
|
11507
|
-
);
|
|
12456
|
+
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
11508
12457
|
const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
|
|
11509
12458
|
send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
|
|
11510
12459
|
} catch {
|
|
@@ -11515,12 +12464,9 @@ function createSessionHandlers(ctx) {
|
|
|
11515
12464
|
if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
|
|
11516
12465
|
const { checkpointIndex } = msg.payload;
|
|
11517
12466
|
try {
|
|
11518
|
-
const { DefaultSessionRewinder } = await import(
|
|
12467
|
+
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
11519
12468
|
const projectRoot = ctx.getProjectRoot();
|
|
11520
|
-
const rewinder = new DefaultSessionRewinder(
|
|
11521
|
-
path5.join(projectRoot, ".wrongstack", "sessions"),
|
|
11522
|
-
projectRoot
|
|
11523
|
-
);
|
|
12469
|
+
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
11524
12470
|
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
11525
12471
|
await ctx.context.session.truncateToCheckpoint(checkpointIndex);
|
|
11526
12472
|
sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
|
|
@@ -11603,11 +12549,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11603
12549
|
const saved = await providerHandlers.loadConfigProviders();
|
|
11604
12550
|
const cfg = saved[providerId];
|
|
11605
12551
|
const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
|
|
12552
|
+
const siblingId = SIBLING_CATALOG[providerId];
|
|
12553
|
+
const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
|
|
11606
12554
|
const models = await enrichProviderModelDescriptors(
|
|
11607
12555
|
deps2.modelsRegistry,
|
|
11608
12556
|
providerId,
|
|
11609
12557
|
cfg,
|
|
11610
|
-
resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId)
|
|
12558
|
+
resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId, siblingCatalog)
|
|
11611
12559
|
);
|
|
11612
12560
|
send(ws, {
|
|
11613
12561
|
type: "provider.models",
|
|
@@ -11633,7 +12581,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11633
12581
|
const providerCfg = newCfg.providers?.[newProvider] ?? {
|
|
11634
12582
|
type: newProvider
|
|
11635
12583
|
};
|
|
11636
|
-
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) :
|
|
12584
|
+
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
11637
12585
|
deps2.context.provider = newProv;
|
|
11638
12586
|
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
11639
12587
|
await cb.updateGlobalConfig((config) => {
|
|
@@ -11734,8 +12682,10 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11734
12682
|
getProjectRoot: state.getProjectRoot,
|
|
11735
12683
|
getSession: state.getSession,
|
|
11736
12684
|
getSessionStore: state.getSessionStore,
|
|
12685
|
+
sessionsDir: deps2.wpaths.projectSessions,
|
|
11737
12686
|
setSession: state.setSession,
|
|
11738
12687
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
12688
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
11739
12689
|
sessionStartPayload: cb.sessionStartPayload
|
|
11740
12690
|
});
|
|
11741
12691
|
const projectRoutes = createProjectHandlers({
|
|
@@ -11830,6 +12780,17 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11830
12780
|
deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
11831
12781
|
}
|
|
11832
12782
|
}
|
|
12783
|
+
if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
|
|
12784
|
+
const { getProcessRegistry } = await import("@wrongstack/tools");
|
|
12785
|
+
getProcessRegistry().setBreakerConfig({
|
|
12786
|
+
...typeof payload["breakerEnabled"] === "boolean" ? { enabled: payload["breakerEnabled"] } : {},
|
|
12787
|
+
...typeof payload["breakerAutoKillResetMs"] === "number" ? { autoKillResetMs: payload["breakerAutoKillResetMs"] } : {}
|
|
12788
|
+
});
|
|
12789
|
+
}
|
|
12790
|
+
if (typeof payload["debugStream"] === "boolean") {
|
|
12791
|
+
const { setDebugStreamEnabled } = await import("@wrongstack/providers");
|
|
12792
|
+
setDebugStreamEnabled(payload["debugStream"]);
|
|
12793
|
+
}
|
|
11833
12794
|
if (typeof payload["logLevel"] === "string") {
|
|
11834
12795
|
const valid = ["debug", "info", "warn", "error"];
|
|
11835
12796
|
if (valid.includes(payload["logLevel"])) {
|
|
@@ -11876,7 +12837,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11876
12837
|
}
|
|
11877
12838
|
return handleMailboxMessages(
|
|
11878
12839
|
ws,
|
|
11879
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
12840
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
|
|
11880
12841
|
parsed.value
|
|
11881
12842
|
);
|
|
11882
12843
|
},
|
|
@@ -11888,13 +12849,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11888
12849
|
}
|
|
11889
12850
|
return handleMailboxAgents(
|
|
11890
12851
|
ws,
|
|
11891
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
12852
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
|
|
11892
12853
|
parsed.value
|
|
11893
12854
|
);
|
|
11894
12855
|
},
|
|
11895
12856
|
clear: (ws) => handleMailboxClear(ws, {
|
|
11896
12857
|
projectRoot: state.getProjectRoot(),
|
|
11897
|
-
globalRoot:
|
|
12858
|
+
globalRoot: path20.dirname(deps2.globalConfigPath)
|
|
11898
12859
|
}),
|
|
11899
12860
|
purge: (ws, msg) => {
|
|
11900
12861
|
const parsed = validateMailboxPurgePayload(msg.payload);
|
|
@@ -11904,9 +12865,16 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11904
12865
|
}
|
|
11905
12866
|
return handleMailboxPurge(
|
|
11906
12867
|
ws,
|
|
11907
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
12868
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
|
|
11908
12869
|
parsed.value
|
|
11909
12870
|
);
|
|
12871
|
+
},
|
|
12872
|
+
compact: (ws, msg) => {
|
|
12873
|
+
return handleMailboxCompact(
|
|
12874
|
+
ws,
|
|
12875
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
|
|
12876
|
+
msg.payload ?? {}
|
|
12877
|
+
);
|
|
11910
12878
|
}
|
|
11911
12879
|
};
|
|
11912
12880
|
const mcpRoutes = {
|
|
@@ -11919,14 +12887,29 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11919
12887
|
sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
11920
12888
|
wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
11921
12889
|
restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
11922
|
-
discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
|
|
12890
|
+
discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12891
|
+
resources: (ws, msg) => handleMcpResources(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12892
|
+
prompts: (ws, msg) => handleMcpPrompts(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12893
|
+
resourceRead: (ws, msg) => handleMcpResourceRead(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12894
|
+
promptGet: (ws, msg) => handleMcpPromptGet(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
|
|
12895
|
+
};
|
|
12896
|
+
const sendBrainStatus = (ws) => {
|
|
12897
|
+
const snapshot = deps2.brainRuntime.getSnapshot();
|
|
12898
|
+
send(ws, {
|
|
12899
|
+
type: "brain.status",
|
|
12900
|
+
payload: {
|
|
12901
|
+
maxAutoRisk: deps2.brainSettings.maxAutoRisk,
|
|
12902
|
+
log: deps2.brainLog,
|
|
12903
|
+
mode: snapshot.mode,
|
|
12904
|
+
poolLabels: snapshot.poolLabels,
|
|
12905
|
+
councilLabels: snapshot.councilLabels,
|
|
12906
|
+
ledgerPath: snapshot.ledger.path
|
|
12907
|
+
}
|
|
12908
|
+
});
|
|
11923
12909
|
};
|
|
11924
12910
|
const brainRoutes = {
|
|
11925
12911
|
status: (ws) => {
|
|
11926
|
-
|
|
11927
|
-
type: "brain.status",
|
|
11928
|
-
payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
|
|
11929
|
-
});
|
|
12912
|
+
sendBrainStatus(ws);
|
|
11930
12913
|
},
|
|
11931
12914
|
risk: (ws, msg) => {
|
|
11932
12915
|
const parsed = validateBrainRiskPayload(msg.payload);
|
|
@@ -11936,11 +12919,43 @@ function buildRoutes(state, deps2, cb) {
|
|
|
11936
12919
|
}
|
|
11937
12920
|
const { level } = parsed.value;
|
|
11938
12921
|
deps2.brainSettings.maxAutoRisk = level;
|
|
12922
|
+
sendBrainStatus(ws);
|
|
12923
|
+
},
|
|
12924
|
+
configGet: (ws) => {
|
|
11939
12925
|
send(ws, {
|
|
11940
|
-
type: "brain.
|
|
11941
|
-
payload: {
|
|
12926
|
+
type: "brain.config",
|
|
12927
|
+
payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
|
|
11942
12928
|
});
|
|
11943
12929
|
},
|
|
12930
|
+
configSet: async (ws, msg) => {
|
|
12931
|
+
const parsed = validateBrainConfigSetPayload(msg.payload);
|
|
12932
|
+
if (!parsed.ok) {
|
|
12933
|
+
sendResult(ws, false, parsed.message);
|
|
12934
|
+
return;
|
|
12935
|
+
}
|
|
12936
|
+
try {
|
|
12937
|
+
const { persisted } = deps2.brainRuntime.apply(parsed.value.patch);
|
|
12938
|
+
const result = await persisted;
|
|
12939
|
+
send(ws, {
|
|
12940
|
+
type: "brain.config",
|
|
12941
|
+
payload: {
|
|
12942
|
+
config: deps2.brainRuntime.getSnapshot(),
|
|
12943
|
+
persisted: result.ok,
|
|
12944
|
+
...result.ok ? {} : { error: result.error ?? "Persist failed." }
|
|
12945
|
+
}
|
|
12946
|
+
});
|
|
12947
|
+
sendBrainStatus(ws);
|
|
12948
|
+
} catch (err) {
|
|
12949
|
+
send(ws, {
|
|
12950
|
+
type: "brain.config",
|
|
12951
|
+
payload: {
|
|
12952
|
+
config: deps2.brainRuntime.getSnapshot(),
|
|
12953
|
+
persisted: false,
|
|
12954
|
+
error: `Invalid Brain setting: ${errMessage(err)}`
|
|
12955
|
+
}
|
|
12956
|
+
});
|
|
12957
|
+
}
|
|
12958
|
+
},
|
|
11944
12959
|
ask: async (ws, msg) => {
|
|
11945
12960
|
const parsed = validateBrainAskPayload(msg.payload);
|
|
11946
12961
|
if (!parsed.ok) {
|
|
@@ -12012,7 +13027,7 @@ async function startWebUI(opts = {}) {
|
|
|
12012
13027
|
const updateGlobalConfig2 = async (mutate, errorLabel) => updateGlobalConfig(prefHelperDeps, configWriteLock, mutate, errorLabel);
|
|
12013
13028
|
console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
|
|
12014
13029
|
if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
|
|
12015
|
-
const firstKey =
|
|
13030
|
+
const firstKey = expectDefined3(Object.keys(config.providers)[0]);
|
|
12016
13031
|
config = patchConfig(config, { provider: firstKey });
|
|
12017
13032
|
console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
|
|
12018
13033
|
}
|
|
@@ -12048,7 +13063,8 @@ async function startWebUI(opts = {}) {
|
|
|
12048
13063
|
promptsCtx,
|
|
12049
13064
|
modelCapabilitiesRef,
|
|
12050
13065
|
provider,
|
|
12051
|
-
context
|
|
13066
|
+
context,
|
|
13067
|
+
sessionIdentity
|
|
12052
13068
|
} = preContext;
|
|
12053
13069
|
let sessionStore = preContext.sessionStore;
|
|
12054
13070
|
let session = preContext.session;
|
|
@@ -12081,7 +13097,12 @@ async function startWebUI(opts = {}) {
|
|
|
12081
13097
|
modelCapabilitiesRef,
|
|
12082
13098
|
sessionGetter: () => session,
|
|
12083
13099
|
sessionReader,
|
|
12084
|
-
annotationsStore
|
|
13100
|
+
annotationsStore,
|
|
13101
|
+
// Brain settings persist to the GLOBAL config only (config.brain is on
|
|
13102
|
+
// the in-project deny list), serialized behind the shared write lock.
|
|
13103
|
+
persistBrainConfig: (brainConfig) => updateGlobalConfig2((decrypted) => {
|
|
13104
|
+
decrypted["brain"] = brainConfig;
|
|
13105
|
+
}, "brain.config")
|
|
12085
13106
|
});
|
|
12086
13107
|
const {
|
|
12087
13108
|
compactor,
|
|
@@ -12091,6 +13112,7 @@ async function startWebUI(opts = {}) {
|
|
|
12091
13112
|
pipelines,
|
|
12092
13113
|
brain,
|
|
12093
13114
|
brainSettings,
|
|
13115
|
+
brainRuntime,
|
|
12094
13116
|
brainLog,
|
|
12095
13117
|
brainMonitor,
|
|
12096
13118
|
codebaseIndexing,
|
|
@@ -12112,7 +13134,7 @@ async function startWebUI(opts = {}) {
|
|
|
12112
13134
|
getProjectRoot: () => projectRoot,
|
|
12113
13135
|
getWorkingDir: () => workingDir,
|
|
12114
13136
|
getModeId: () => modeId,
|
|
12115
|
-
getContextMode: () => String(context.meta["contextWindowMode"] ??
|
|
13137
|
+
getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2),
|
|
12116
13138
|
getNeedsSetup: () => needsSetup,
|
|
12117
13139
|
modelsRegistry
|
|
12118
13140
|
});
|
|
@@ -12155,21 +13177,21 @@ async function startWebUI(opts = {}) {
|
|
|
12155
13177
|
wpaths
|
|
12156
13178
|
}, watcherMetricsRef);
|
|
12157
13179
|
async function touchProjectEntry(root, workDir) {
|
|
12158
|
-
const resolved =
|
|
13180
|
+
const resolved = path21.resolve(root);
|
|
12159
13181
|
const manifest = await loadManifest(globalConfigPath);
|
|
12160
13182
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12161
|
-
const existing = manifest.projects.find((p) =>
|
|
13183
|
+
const existing = manifest.projects.find((p) => path21.resolve(p.root) === resolved);
|
|
12162
13184
|
if (existing) {
|
|
12163
13185
|
existing.lastSeen = now;
|
|
12164
|
-
if (workDir) existing.lastWorkingDir =
|
|
13186
|
+
if (workDir) existing.lastWorkingDir = path21.resolve(workDir);
|
|
12165
13187
|
} else {
|
|
12166
13188
|
manifest.projects.push({
|
|
12167
|
-
name:
|
|
13189
|
+
name: path21.basename(resolved),
|
|
12168
13190
|
root: resolved,
|
|
12169
13191
|
slug: generateProjectSlug(resolved),
|
|
12170
13192
|
createdAt: now,
|
|
12171
13193
|
lastSeen: now,
|
|
12172
|
-
lastWorkingDir: workDir ?
|
|
13194
|
+
lastWorkingDir: workDir ? path21.resolve(workDir) : void 0
|
|
12173
13195
|
});
|
|
12174
13196
|
}
|
|
12175
13197
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -12261,10 +13283,12 @@ async function startWebUI(opts = {}) {
|
|
|
12261
13283
|
terminalHandler,
|
|
12262
13284
|
brain,
|
|
12263
13285
|
brainSettings,
|
|
13286
|
+
brainRuntime,
|
|
12264
13287
|
brainLog
|
|
12265
13288
|
};
|
|
12266
13289
|
const cb = {
|
|
12267
13290
|
sessionStartPayload,
|
|
13291
|
+
onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
|
|
12268
13292
|
updateAutoCompactionMaxContext,
|
|
12269
13293
|
updateGlobalConfig: updateGlobalConfig2,
|
|
12270
13294
|
persistPrefsToConfig: persistPrefsToConfig2,
|
|
@@ -12314,13 +13338,13 @@ async function startWebUI(opts = {}) {
|
|
|
12314
13338
|
...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
|
|
12315
13339
|
...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
|
|
12316
13340
|
};
|
|
12317
|
-
const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) :
|
|
13341
|
+
const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig3(activeId, { ...providerCfg, type: activeId });
|
|
12318
13342
|
deps2.context.provider = newProv;
|
|
12319
13343
|
void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
|
|
12320
13344
|
console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
|
|
12321
13345
|
} catch (err) {
|
|
12322
13346
|
console.warn(
|
|
12323
|
-
`[WebUI] Credential hot-reload failed for ${activeId}: ${
|
|
13347
|
+
`[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage10(err)}`
|
|
12324
13348
|
);
|
|
12325
13349
|
}
|
|
12326
13350
|
},
|
|
@@ -12387,17 +13411,27 @@ async function startWebUI(opts = {}) {
|
|
|
12387
13411
|
},
|
|
12388
13412
|
clients: () => clients.keys(),
|
|
12389
13413
|
servers: [httpServer, wssPrimary, ...wssSecondary ? [wssSecondary] : []],
|
|
12390
|
-
onShutdown: () => {
|
|
13414
|
+
onShutdown: async () => {
|
|
12391
13415
|
credentialWatcherClose?.();
|
|
12392
13416
|
brainMonitor.stop();
|
|
12393
|
-
|
|
13417
|
+
await agentServices.brainLedger?.stop().catch(() => {
|
|
13418
|
+
});
|
|
13419
|
+
await mcpRegistry.stopAll().catch(() => void 0);
|
|
13420
|
+
await sessionIdentity.stop();
|
|
12394
13421
|
eventArming.getDispose()?.();
|
|
12395
13422
|
if (eternalSubscription) {
|
|
12396
13423
|
eternalSubscription.dispose();
|
|
12397
13424
|
eternalSubscription = null;
|
|
12398
13425
|
}
|
|
12399
13426
|
codebaseIndexing.dispose();
|
|
12400
|
-
|
|
13427
|
+
if (config.superMemory?.enabled !== false && config.superMemory?.hygiene?.autoAfterSession !== false) {
|
|
13428
|
+
const candidate = memoryStore;
|
|
13429
|
+
await candidate.hygiene?.({
|
|
13430
|
+
retentionDays: config.superMemory?.hygiene?.retentionDays,
|
|
13431
|
+
archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
|
|
13432
|
+
}).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
|
|
13433
|
+
}
|
|
13434
|
+
await unregisterInstance(process.pid, path21.dirname(globalConfigPath));
|
|
12401
13435
|
}
|
|
12402
13436
|
});
|
|
12403
13437
|
}
|
|
@@ -12412,7 +13446,7 @@ function readArg(names) {
|
|
|
12412
13446
|
if (current === name2) {
|
|
12413
13447
|
const next = argv[i + 1];
|
|
12414
13448
|
if (!next || next.startsWith("-")) {
|
|
12415
|
-
throw new
|
|
13449
|
+
throw new ToolValidationError6({ message: `${name2} requires a value`, field: name2 });
|
|
12416
13450
|
}
|
|
12417
13451
|
return next;
|
|
12418
13452
|
}
|
|
@@ -12425,7 +13459,7 @@ function parsePort(value, fallback, label) {
|
|
|
12425
13459
|
if (value === void 0) return fallback;
|
|
12426
13460
|
const parsed = Number.parseInt(value, 10);
|
|
12427
13461
|
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
12428
|
-
throw new
|
|
13462
|
+
throw new ToolValidationError6({
|
|
12429
13463
|
message: `${label} must be a port between 1 and 65535`,
|
|
12430
13464
|
field: "port"
|
|
12431
13465
|
});
|
|
@@ -12517,4 +13551,3 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
12517
13551
|
});
|
|
12518
13552
|
}
|
|
12519
13553
|
//# sourceMappingURL=entry.js.map
|
|
12520
|
-
//# sourceMappingURL=entry.js.map
|