@wrongstack/webui-server 0.285.0 → 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.
Files changed (41) hide show
  1. package/dist/index.js +827 -301
  2. package/dist/index.js.map +4 -4
  3. package/dist/server/backend-services.d.ts +12 -23
  4. package/dist/server/backend-services.d.ts.map +1 -1
  5. package/dist/server/brain-routes.d.ts +2 -0
  6. package/dist/server/brain-routes.d.ts.map +1 -1
  7. package/dist/server/collaboration-ws-handler.d.ts +27 -19
  8. package/dist/server/collaboration-ws-handler.d.ts.map +1 -1
  9. package/dist/server/context-meta.d.ts.map +1 -1
  10. package/dist/server/entry.js +823 -301
  11. package/dist/server/entry.js.map +4 -4
  12. package/dist/server/handlers.js.map +2 -2
  13. package/dist/server/index.d.ts +2 -2
  14. package/dist/server/index.d.ts.map +1 -1
  15. package/dist/server/kanban-routes.d.ts.map +1 -1
  16. package/dist/server/mailbox-handlers.d.ts +12 -0
  17. package/dist/server/mailbox-handlers.d.ts.map +1 -1
  18. package/dist/server/mailbox-routes.d.ts +1 -0
  19. package/dist/server/mailbox-routes.d.ts.map +1 -1
  20. package/dist/server/mcp-handlers.d.ts +10 -1
  21. package/dist/server/mcp-handlers.d.ts.map +1 -1
  22. package/dist/server/mcp-routes.d.ts +4 -0
  23. package/dist/server/mcp-routes.d.ts.map +1 -1
  24. package/dist/server/message-dispatcher.d.ts.map +1 -1
  25. package/dist/server/pre-context-services.d.ts +3 -1
  26. package/dist/server/pre-context-services.d.ts.map +1 -1
  27. package/dist/server/pref-helpers.d.ts +1 -1
  28. package/dist/server/pref-helpers.d.ts.map +1 -1
  29. package/dist/server/routes.d.ts +6 -1
  30. package/dist/server/routes.d.ts.map +1 -1
  31. package/dist/server/server-runtime.d.ts.map +1 -1
  32. package/dist/server/session-handlers.d.ts +13 -0
  33. package/dist/server/session-handlers.d.ts.map +1 -1
  34. package/dist/server/setup-events.d.ts +1 -1
  35. package/dist/server/setup-events.d.ts.map +1 -1
  36. package/dist/server/standalone-session-identity.d.ts +35 -0
  37. package/dist/server/standalone-session-identity.d.ts.map +1 -0
  38. package/dist/server/start-webui.d.ts.map +1 -1
  39. package/dist/server/ws-payload-validation.d.ts +4 -0
  40. package/dist/server/ws-payload-validation.d.ts.map +1 -1
  41. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -2996,7 +2996,7 @@ function mapStatus(raw) {
2996
2996
  return "stopped";
2997
2997
  }
2998
2998
  }
2999
- function toView(info) {
2999
+ function toView(info, health) {
3000
3000
  const view = {
3001
3001
  name: info.name,
3002
3002
  transport: info.transport,
@@ -3008,6 +3008,7 @@ function toView(info) {
3008
3008
  };
3009
3009
  if (info.description !== void 0) view.description = info.description;
3010
3010
  if (info.lazy !== void 0) view.lazy = info.lazy;
3011
+ if (health !== void 0) view.health = health;
3011
3012
  return view;
3012
3013
  }
3013
3014
  function deps(ws, globalConfigPath, registry) {
@@ -3033,7 +3034,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
3033
3034
  registry: mcpRegistry,
3034
3035
  presets: allServers()
3035
3036
  });
3036
- send(ws, { type: "mcp.list", payload: { servers: servers.map(toView) } });
3037
+ const health = new Map(
3038
+ (typeof mcpRegistry.operationalHealth === "function" ? mcpRegistry.operationalHealth() : []).map((item) => [item.name, item])
3039
+ );
3040
+ send(ws, {
3041
+ type: "mcp.list",
3042
+ payload: { servers: servers.map((server) => toView(server, health.get(server.name))) }
3043
+ });
3037
3044
  }
3038
3045
  async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
3039
3046
  const d = deps(ws, globalConfigPath, mcpRegistry);
@@ -3183,6 +3190,99 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
3183
3190
  payload: { success: result.ok, message: result.message }
3184
3191
  });
3185
3192
  }
3193
+ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3194
+ if (!mcpRegistry) return sendContentError(ws, "resources", "", "MCP registry is not available.");
3195
+ const payload = payloadRecord(msg);
3196
+ let serverName = "";
3197
+ try {
3198
+ serverName = requiredPayloadString(payload, "name");
3199
+ const refresh = payload["refresh"] === true;
3200
+ const [resources, resourceTemplates] = await Promise.all([
3201
+ mcpRegistry.listResources(serverName, { refresh }),
3202
+ mcpRegistry.listResourceTemplates(serverName, { refresh })
3203
+ ]);
3204
+ send(ws, {
3205
+ type: "mcp.resources",
3206
+ payload: { name: serverName, resources, resourceTemplates }
3207
+ });
3208
+ } catch (err) {
3209
+ sendContentError(ws, "resources", serverName, errorMessage(err));
3210
+ }
3211
+ }
3212
+ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3213
+ if (!mcpRegistry) return sendContentError(ws, "prompts", "", "MCP registry is not available.");
3214
+ const payload = payloadRecord(msg);
3215
+ let serverName = "";
3216
+ try {
3217
+ serverName = requiredPayloadString(payload, "name");
3218
+ const prompts = await mcpRegistry.listPrompts(serverName, {
3219
+ refresh: payload["refresh"] === true
3220
+ });
3221
+ send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3222
+ } catch (err) {
3223
+ sendContentError(ws, "prompts", serverName, errorMessage(err));
3224
+ }
3225
+ }
3226
+ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3227
+ if (!mcpRegistry)
3228
+ return sendContentError(ws, "resource.read", "", "MCP registry is not available.");
3229
+ const payload = payloadRecord(msg);
3230
+ let serverName = "";
3231
+ try {
3232
+ serverName = requiredPayloadString(payload, "name");
3233
+ const insertion = await mcpRegistry.selectResourceForInsertion(
3234
+ serverName,
3235
+ requiredPayloadString(payload, "uri")
3236
+ );
3237
+ send(ws, { type: "mcp.content.selected", payload: insertion });
3238
+ } catch (err) {
3239
+ sendContentError(ws, "resource.read", serverName, errorMessage(err));
3240
+ }
3241
+ }
3242
+ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3243
+ if (!mcpRegistry) return sendContentError(ws, "prompt.get", "", "MCP registry is not available.");
3244
+ const payload = payloadRecord(msg);
3245
+ let serverName = "";
3246
+ try {
3247
+ serverName = requiredPayloadString(payload, "name");
3248
+ const insertion = await mcpRegistry.selectPromptForInsertion(
3249
+ serverName,
3250
+ requiredPayloadString(payload, "prompt"),
3251
+ promptArguments(payload["arguments"])
3252
+ );
3253
+ send(ws, { type: "mcp.content.selected", payload: insertion });
3254
+ } catch (err) {
3255
+ sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3256
+ }
3257
+ }
3258
+ function payloadRecord(msg) {
3259
+ return msg.payload && typeof msg.payload === "object" && !Array.isArray(msg.payload) ? msg.payload : {};
3260
+ }
3261
+ function requiredPayloadString(payload, field) {
3262
+ const value = payload[field];
3263
+ if (typeof value !== "string" || value.length === 0) {
3264
+ throw new Error(`MCP payload field "${field}" must be a non-empty string`);
3265
+ }
3266
+ return value;
3267
+ }
3268
+ function promptArguments(value) {
3269
+ if (value === void 0) return void 0;
3270
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3271
+ throw new Error("MCP prompt arguments must be an object");
3272
+ }
3273
+ const args = {};
3274
+ for (const [key, item] of Object.entries(value)) {
3275
+ if (typeof item !== "string") throw new Error(`MCP prompt argument "${key}" must be a string`);
3276
+ args[key] = item;
3277
+ }
3278
+ return args;
3279
+ }
3280
+ function sendContentError(ws, action, name2, error) {
3281
+ send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3282
+ }
3283
+ function errorMessage(err) {
3284
+ return err instanceof Error ? err.message : String(err);
3285
+ }
3186
3286
 
3187
3287
  // src/server/memory-handlers.ts
3188
3288
  async function handleMemoryList(ws, memoryStore) {
@@ -4272,6 +4372,16 @@ function validateBrainAskPayload(payload) {
4272
4372
  }
4273
4373
  return { ok: true, value: { question: question.trim() } };
4274
4374
  }
4375
+ function validateBrainConfigSetPayload(payload) {
4376
+ if (!isRecord(payload)) {
4377
+ return { ok: false, message: "brain.config.set payload must be an object with a patch object" };
4378
+ }
4379
+ const patch = payload["patch"];
4380
+ if (!isRecord(patch)) {
4381
+ return { ok: false, message: "brain.config.set payload.patch must be an object" };
4382
+ }
4383
+ return { ok: true, value: { patch } };
4384
+ }
4275
4385
  function validateAutonomySwitchPayload(payload) {
4276
4386
  if (!isRecord(payload)) {
4277
4387
  return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
@@ -4333,7 +4443,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4333
4443
  "hqEnabled",
4334
4444
  "hqRawContent",
4335
4445
  "fallbackAuto",
4336
- "favoriteModelsOnly"
4446
+ "favoriteModelsOnly",
4447
+ "breakerEnabled",
4448
+ "debugStream"
4337
4449
  ]);
4338
4450
  var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4339
4451
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
@@ -4344,9 +4456,17 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4344
4456
  "maxIterations",
4345
4457
  "maxConcurrent",
4346
4458
  "enhanceDelayMs",
4347
- "tgLongToolMs"
4459
+ "tgLongToolMs",
4460
+ "breakerAutoKillResetMs"
4461
+ ]);
4462
+ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4463
+ "hqUrl",
4464
+ "hqToken",
4465
+ "uiLocale",
4466
+ "thinkingWord",
4467
+ "refinerProvider",
4468
+ "refinerModel"
4348
4469
  ]);
4349
- var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
4350
4470
  var ENUM_PREF_KEYS = {
4351
4471
  autonomy: AUTONOMY_VALUES,
4352
4472
  contextStrategy: CONTEXT_STRATEGY_VALUES,
@@ -4357,7 +4477,10 @@ var ENUM_PREF_KEYS = {
4357
4477
  auditLevel: AUDIT_LEVEL_VALUES,
4358
4478
  reasoningMode: REASONING_MODE_VALUES,
4359
4479
  reasoningEffort: REASONING_EFFORT_VALUES,
4360
- cacheTtl: CACHE_TTL_VALUES
4480
+ cacheTtl: CACHE_TTL_VALUES,
4481
+ statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4482
+ animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4483
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
4361
4484
  };
4362
4485
  function validateModelRuntimeValue(modelRuntime, path23) {
4363
4486
  const reasoning = modelRuntime["reasoning"];
@@ -5756,6 +5879,10 @@ function shouldLogWatcherStats() {
5756
5879
  function setupEvents(deps2) {
5757
5880
  const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5758
5881
  const disposers = [];
5882
+ let disposed = false;
5883
+ const on = (event, listener) => {
5884
+ disposers.push(events.on(event, listener));
5885
+ };
5759
5886
  const currentSessionId = () => context.session?.id ?? "";
5760
5887
  const sessionPayload2 = (payload) => {
5761
5888
  const provided = payload["sessionId"];
@@ -5771,20 +5898,20 @@ function setupEvents(deps2) {
5771
5898
  sessionBridge?.append(event).catch(() => {
5772
5899
  });
5773
5900
  };
5774
- events.on("iteration.started", (e) => {
5901
+ on("iteration.started", (e) => {
5775
5902
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
5776
5903
  broadcast2(clients, {
5777
5904
  type: "iteration.started",
5778
5905
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
5779
5906
  });
5780
5907
  });
5781
- events.on("iteration.completed", (e) => {
5908
+ on("iteration.completed", (e) => {
5782
5909
  broadcast2(clients, {
5783
5910
  type: "iteration.completed",
5784
5911
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
5785
5912
  });
5786
5913
  });
5787
- events.on("iteration.limit_reached", (e) => {
5914
+ on("iteration.limit_reached", (e) => {
5788
5915
  broadcast2(clients, {
5789
5916
  type: "iteration.limit_reached",
5790
5917
  payload: sessionPayload2({
@@ -5794,19 +5921,19 @@ function setupEvents(deps2) {
5794
5921
  })
5795
5922
  });
5796
5923
  });
5797
- events.on("provider.text_delta", (e) => {
5924
+ on("provider.text_delta", (e) => {
5798
5925
  broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5799
5926
  });
5800
- events.on("provider.thinking_delta", (e) => {
5927
+ on("provider.thinking_delta", (e) => {
5801
5928
  broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5802
5929
  });
5803
- events.on("provider.stream_error", (e) => {
5930
+ on("provider.stream_error", (e) => {
5804
5931
  broadcast2(clients, {
5805
5932
  type: "provider.stream_error",
5806
5933
  payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
5807
5934
  });
5808
5935
  });
5809
- events.on("tool.started", (e) => {
5936
+ on("tool.started", (e) => {
5810
5937
  broadcast2(clients, {
5811
5938
  type: "tool.started",
5812
5939
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
@@ -5819,7 +5946,7 @@ function setupEvents(deps2) {
5819
5946
  input: e.input
5820
5947
  });
5821
5948
  });
5822
- events.on("tool.progress", (e) => {
5949
+ on("tool.progress", (e) => {
5823
5950
  broadcast2(clients, {
5824
5951
  type: "tool.progress",
5825
5952
  // Nested `event` shape — the client handler reads `payload.event?.text`
@@ -5840,7 +5967,7 @@ function setupEvents(deps2) {
5840
5967
  }
5841
5968
  });
5842
5969
  });
5843
- events.on("tool.executed", (e) => {
5970
+ on("tool.executed", (e) => {
5844
5971
  broadcast2(clients, {
5845
5972
  type: "tool.executed",
5846
5973
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
@@ -5898,7 +6025,7 @@ function setupEvents(deps2) {
5898
6025
  })();
5899
6026
  }
5900
6027
  });
5901
- events.on("tool.loop_detected", (e) => {
6028
+ on("tool.loop_detected", (e) => {
5902
6029
  broadcast2(clients, {
5903
6030
  type: "tool.loop_detected",
5904
6031
  payload: sessionPayload2({
@@ -5910,19 +6037,19 @@ function setupEvents(deps2) {
5910
6037
  })
5911
6038
  });
5912
6039
  });
5913
- events.on("trust.persisted", (e) => {
6040
+ on("trust.persisted", (e) => {
5914
6041
  broadcast2(clients, {
5915
6042
  type: "trust.persisted",
5916
6043
  payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
5917
6044
  });
5918
6045
  });
5919
- events.on("delegate.started", (e) => {
6046
+ on("delegate.started", (e) => {
5920
6047
  broadcast2(clients, {
5921
6048
  type: "delegate.started",
5922
6049
  payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
5923
6050
  });
5924
6051
  });
5925
- events.on("delegate.completed", (e) => {
6052
+ on("delegate.completed", (e) => {
5926
6053
  broadcast2(clients, {
5927
6054
  type: "delegate.completed",
5928
6055
  payload: sessionPayload2({
@@ -5940,7 +6067,7 @@ function setupEvents(deps2) {
5940
6067
  })
5941
6068
  });
5942
6069
  });
5943
- events.on("provider.response", (e) => {
6070
+ on("provider.response", (e) => {
5944
6071
  broadcast2(clients, {
5945
6072
  type: "provider.response",
5946
6073
  payload: sessionPayload2({
@@ -5952,7 +6079,7 @@ function setupEvents(deps2) {
5952
6079
  })
5953
6080
  });
5954
6081
  });
5955
- events.on("ctx.pct", (e) => {
6082
+ on("ctx.pct", (e) => {
5956
6083
  broadcast2(clients, {
5957
6084
  type: "ctx.pct",
5958
6085
  payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
@@ -5969,28 +6096,28 @@ function setupEvents(deps2) {
5969
6096
  })
5970
6097
  });
5971
6098
  });
5972
- events.on("ctx.max_context", (e) => {
6099
+ on("ctx.max_context", (e) => {
5973
6100
  broadcast2(clients, {
5974
6101
  type: "ctx.max_context",
5975
6102
  payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
5976
6103
  });
5977
6104
  });
5978
- events.on("token.threshold", (e) => {
6105
+ on("token.threshold", (e) => {
5979
6106
  broadcast2(clients, {
5980
6107
  type: "token.threshold",
5981
6108
  payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
5982
6109
  });
5983
6110
  });
5984
- events.on("token.cost_estimate_unavailable", (e) => {
6111
+ on("token.cost_estimate_unavailable", (e) => {
5985
6112
  broadcast2(clients, {
5986
6113
  type: "token.cost_estimate_unavailable",
5987
6114
  payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
5988
6115
  });
5989
6116
  });
5990
- events.on("context.repaired", (e) => {
6117
+ on("context.repaired", (e) => {
5991
6118
  broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5992
6119
  });
5993
- events.on("tool.confirm_needed", (e) => {
6120
+ on("tool.confirm_needed", (e) => {
5994
6121
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
5995
6122
  const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
5996
6123
  pendingConfirms.set(id, {
@@ -6001,7 +6128,7 @@ function setupEvents(deps2) {
6001
6128
  });
6002
6129
  broadcast2(clients, { type: "tool.confirm_needed", payload });
6003
6130
  });
6004
- events.on("error", (e) => {
6131
+ on("error", (e) => {
6005
6132
  broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
6006
6133
  appendForCurrentSession(e.sessionId, {
6007
6134
  type: "error",
@@ -6010,13 +6137,13 @@ function setupEvents(deps2) {
6010
6137
  phase: e.phase
6011
6138
  });
6012
6139
  });
6013
- events.on("session.damaged", (e) => {
6140
+ on("session.damaged", (e) => {
6014
6141
  broadcast2(clients, {
6015
6142
  type: "session.damaged",
6016
6143
  payload: { sessionId: e.sessionId, detail: e.detail }
6017
6144
  });
6018
6145
  });
6019
- events.on("session.rewound", (e) => {
6146
+ on("session.rewound", (e) => {
6020
6147
  broadcast2(clients, {
6021
6148
  type: "session.rewound",
6022
6149
  payload: sessionPayload2({
@@ -6027,7 +6154,7 @@ function setupEvents(deps2) {
6027
6154
  })
6028
6155
  });
6029
6156
  });
6030
- events.on("checkpoint.written", (e) => {
6157
+ on("checkpoint.written", (e) => {
6031
6158
  broadcast2(clients, {
6032
6159
  type: "checkpoint.written",
6033
6160
  payload: sessionPayload2({
@@ -6039,19 +6166,19 @@ function setupEvents(deps2) {
6039
6166
  })
6040
6167
  });
6041
6168
  });
6042
- events.on("in_flight.started", (e) => {
6169
+ on("in_flight.started", (e) => {
6043
6170
  broadcast2(clients, {
6044
6171
  type: "in_flight.started",
6045
6172
  payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
6046
6173
  });
6047
6174
  });
6048
- events.on("in_flight.ended", (e) => {
6175
+ on("in_flight.ended", (e) => {
6049
6176
  broadcast2(clients, {
6050
6177
  type: "in_flight.ended",
6051
6178
  payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
6052
6179
  });
6053
6180
  });
6054
- events.on("provider.retry", (e) => {
6181
+ on("provider.retry", (e) => {
6055
6182
  broadcast2(clients, {
6056
6183
  type: "provider.retry",
6057
6184
  payload: sessionPayload2({
@@ -6073,7 +6200,7 @@ function setupEvents(deps2) {
6073
6200
  description: e.description
6074
6201
  });
6075
6202
  });
6076
- events.on("provider.error", (e) => {
6203
+ on("provider.error", (e) => {
6077
6204
  broadcast2(clients, {
6078
6205
  type: "provider.error",
6079
6206
  payload: sessionPayload2({
@@ -6093,7 +6220,7 @@ function setupEvents(deps2) {
6093
6220
  retryable: e.retryable
6094
6221
  });
6095
6222
  });
6096
- events.on("provider.fallback", (e) => {
6223
+ on("provider.fallback", (e) => {
6097
6224
  broadcast2(clients, {
6098
6225
  type: "provider.fallback",
6099
6226
  payload: sessionPayload2({
@@ -6105,7 +6232,7 @@ function setupEvents(deps2) {
6105
6232
  })
6106
6233
  });
6107
6234
  });
6108
- events.on("compaction.fired", (e) => {
6235
+ on("compaction.fired", (e) => {
6109
6236
  broadcast2(clients, {
6110
6237
  type: "context.compacted",
6111
6238
  payload: sessionPayload2({
@@ -6117,7 +6244,7 @@ function setupEvents(deps2) {
6117
6244
  })
6118
6245
  });
6119
6246
  });
6120
- events.on("compaction.failed", (e) => {
6247
+ on("compaction.failed", (e) => {
6121
6248
  broadcast2(clients, {
6122
6249
  type: "compaction.failed",
6123
6250
  payload: sessionPayload2({
@@ -6132,25 +6259,25 @@ function setupEvents(deps2) {
6132
6259
  })
6133
6260
  });
6134
6261
  });
6135
- events.on("mcp.server.connected", (e) => {
6262
+ on("mcp.server.connected", (e) => {
6136
6263
  broadcast2(clients, {
6137
6264
  type: "mcp.server.connected",
6138
6265
  payload: { name: e.name, toolCount: e.toolCount }
6139
6266
  });
6140
6267
  });
6141
- events.on("mcp.server.reconnected", (e) => {
6268
+ on("mcp.server.reconnected", (e) => {
6142
6269
  broadcast2(clients, {
6143
6270
  type: "mcp.server.reconnected",
6144
6271
  payload: { name: e.name, toolCount: e.toolCount }
6145
6272
  });
6146
6273
  });
6147
- events.on("mcp.server.disconnected", (e) => {
6274
+ on("mcp.server.disconnected", (e) => {
6148
6275
  broadcast2(clients, {
6149
6276
  type: "mcp.server.disconnected",
6150
6277
  payload: { name: e.name, reason: e.reason }
6151
6278
  });
6152
6279
  });
6153
- events.on("coordinator.stats", (e) => {
6280
+ on("coordinator.stats", (e) => {
6154
6281
  broadcast2(clients, {
6155
6282
  type: "coordinator.stats",
6156
6283
  payload: sessionPayload2({
@@ -6178,16 +6305,16 @@ function setupEvents(deps2) {
6178
6305
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
6179
6306
  });
6180
6307
  const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
6181
- events.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 }));
6182
- events.on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6183
- events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6184
- events.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 }));
6185
- events.on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6186
- events.on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6187
- events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6188
- events.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 }));
6189
- events.on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
6190
- events.on("agent.timeline.message", (e) => {
6308
+ 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 }));
6309
+ on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6310
+ on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6311
+ 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 }));
6312
+ on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6313
+ on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6314
+ on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6315
+ 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 }));
6316
+ on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
6317
+ on("agent.timeline.message", (e) => {
6191
6318
  broadcast2(clients, {
6192
6319
  type: "agent.timeline.message",
6193
6320
  payload: sessionPayload2({
@@ -6203,7 +6330,7 @@ function setupEvents(deps2) {
6203
6330
  })
6204
6331
  });
6205
6332
  });
6206
- events.on("agent.status_changed", (e) => {
6333
+ on("agent.status_changed", (e) => {
6207
6334
  broadcast2(clients, {
6208
6335
  type: "agent.status_changed",
6209
6336
  payload: sessionPayload2({
@@ -6218,7 +6345,7 @@ function setupEvents(deps2) {
6218
6345
  });
6219
6346
  });
6220
6347
  let leaderSpawned = false;
6221
- events.on("iteration.started", (e) => {
6348
+ on("iteration.started", (e) => {
6222
6349
  if (!leaderSpawned) {
6223
6350
  leaderSpawned = true;
6224
6351
  const provider = context.provider?.id ?? "unknown";
@@ -6232,7 +6359,7 @@ function setupEvents(deps2) {
6232
6359
  });
6233
6360
  }
6234
6361
  });
6235
- events.on("tool.executed", (e) => {
6362
+ on("tool.executed", (e) => {
6236
6363
  forwardSubagent("tool_executed", {
6237
6364
  sessionId: e.sessionId,
6238
6365
  subagentId: "leader",
@@ -6241,7 +6368,7 @@ function setupEvents(deps2) {
6241
6368
  ok: e.ok
6242
6369
  });
6243
6370
  });
6244
- events.on("provider.response", (e) => {
6371
+ on("provider.response", (e) => {
6245
6372
  if (e.usage?.input != null) {
6246
6373
  const maxCtx = context.provider.capabilities.maxContext;
6247
6374
  const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
@@ -6258,7 +6385,7 @@ function setupEvents(deps2) {
6258
6385
  });
6259
6386
  }
6260
6387
  });
6261
- events.on("iteration.completed", (e) => {
6388
+ on("iteration.completed", (e) => {
6262
6389
  if (!leaderSpawned) {
6263
6390
  leaderSpawned = true;
6264
6391
  const provider = context.provider?.id ?? "unknown";
@@ -6290,7 +6417,7 @@ function setupEvents(deps2) {
6290
6417
  payload: sessionPayload2({ event: eventName, ...payload })
6291
6418
  });
6292
6419
  });
6293
- events.on("client.status", async (e) => {
6420
+ on("client.status", async (e) => {
6294
6421
  broadcast2(clients, { type: "client.status_update", payload: e });
6295
6422
  if (wpaths?.projectStatus) {
6296
6423
  try {
@@ -6376,6 +6503,7 @@ function setupEvents(deps2) {
6376
6503
  const startWatcher = async () => {
6377
6504
  try {
6378
6505
  await fs10.mkdir(projectsDir, { recursive: true });
6506
+ if (disposed) return;
6379
6507
  watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6380
6508
  if (eventType !== "change" && eventType !== "rename") return;
6381
6509
  if (filename == null) return;
@@ -6406,7 +6534,7 @@ function setupEvents(deps2) {
6406
6534
  );
6407
6535
  }
6408
6536
  };
6409
- events.on("client.status", (e) => {
6537
+ on("client.status", (e) => {
6410
6538
  if (e.projectHash) {
6411
6539
  const hash = String(e.projectHash);
6412
6540
  if (!knownProjectHashes.has(hash)) {
@@ -6446,7 +6574,9 @@ function setupEvents(deps2) {
6446
6574
  const registry = new SessionRegistry(globalRoot);
6447
6575
  const sessions = await registry.list();
6448
6576
  const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
6449
- const live = sessions.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6577
+ const live = sessions.filter(
6578
+ (s) => s.status === "active" || s.status === "idle"
6579
+ ).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6450
6580
  sessionId: s.sessionId,
6451
6581
  projectName: s.projectName,
6452
6582
  projectSlug: s.projectSlug,
@@ -6500,6 +6630,8 @@ function setupEvents(deps2) {
6500
6630
  void broadcastSessions();
6501
6631
  }
6502
6632
  return () => {
6633
+ if (disposed) return;
6634
+ disposed = true;
6503
6635
  for (const dispose of disposers) {
6504
6636
  try {
6505
6637
  dispose();
@@ -6726,7 +6858,7 @@ function createWsServers(ports, accessToken) {
6726
6858
  allowedHostnames: publicHostnames,
6727
6859
  allowBrowserUrlToken: Boolean(ports.publicWsUrl)
6728
6860
  });
6729
- const WS_MAX_PAYLOAD = 8 * 1024 * 1024;
6861
+ const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
6730
6862
  const wssPrimary = new WebSocketServer({
6731
6863
  port: ports.wsPort,
6732
6864
  host: ports.wsHost,
@@ -6834,12 +6966,10 @@ function registerShutdown(deps2) {
6834
6966
  }
6835
6967
 
6836
6968
  // src/server/pre-context-services.ts
6837
- import * as path15 from "node:path";
6969
+ import * as path16 from "node:path";
6838
6970
  import { createRequire as createRequire2 } from "node:module";
6839
- import { WebSocket as WebSocket2 } from "ws";
6840
6971
  import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
6841
6972
  import {
6842
- AgentStatusTracker,
6843
6973
  AnnotationsStore,
6844
6974
  DEFAULT_SESSION_PRUNE_DAYS,
6845
6975
  DefaultModelsRegistry,
@@ -6850,7 +6980,6 @@ import {
6850
6980
  DefaultSkillLoader,
6851
6981
  DefaultSystemPromptBuilder,
6852
6982
  EventBus,
6853
- FleetNotifier,
6854
6983
  GlobalMailbox,
6855
6984
  PromptUsageStore,
6856
6985
  ProviderRegistry,
@@ -6861,8 +6990,8 @@ import {
6861
6990
  applyToolDescriptionModes,
6862
6991
  applyToolResultRenderModes,
6863
6992
  configureChildEnvGitIdentity,
6993
+ getSessionRegistry as getSessionRegistry2,
6864
6994
  resolveContextWindowPolicy,
6865
- getSessionRegistry,
6866
6995
  makeMailboxTool,
6867
6996
  makeMailInboxTool,
6868
6997
  makeFleetStatusTool,
@@ -6871,7 +7000,12 @@ import {
6871
7000
  import {
6872
7001
  createSuperMemoryTools
6873
7002
  } from "@wrongstack/super-memory";
6874
- import { MCPRegistry } from "@wrongstack/mcp";
7003
+ import {
7004
+ MCPAuthorizationManager,
7005
+ MCPRegistry,
7006
+ MCPVaultTokenStore,
7007
+ createVaultBackedMcpAuthorizationProviderFactory
7008
+ } from "@wrongstack/mcp";
6875
7009
  import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
6876
7010
  import { createDefaultContainer } from "@wrongstack/runtime";
6877
7011
  import {
@@ -6883,7 +7017,7 @@ import {
6883
7017
  rememberTool,
6884
7018
  searchMemoryTool
6885
7019
  } from "@wrongstack/tools";
6886
- import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
7020
+ import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6887
7021
 
6888
7022
  // src/server/setup-screen.ts
6889
7023
  import { expectDefined as expectDefined2 } from "@wrongstack/core";
@@ -7021,6 +7155,20 @@ function seedContextMeta(config, context) {
7021
7155
  meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
7022
7156
  meta["cacheTtl"] = mr.cache?.ttl ?? "default";
7023
7157
  }
7158
+ meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
7159
+ meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
7160
+ meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7161
+ meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7162
+ meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
7163
+ meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7164
+ meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7165
+ {
7166
+ const featuresAllow = config.features?.allowOutsideProjectRoot;
7167
+ const toolsRestrict = config.tools?.restrictToProjectRoot;
7168
+ const allow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
7169
+ meta["fsAccess"] = allow ? "unrestricted" : "project";
7170
+ }
7171
+ meta["debugStream"] = config.debugStream === true;
7024
7172
  const hqConfig = config.hq;
7025
7173
  meta["hqEnabled"] = hqConfig?.enabled === true;
7026
7174
  meta["hqUrl"] = hqConfig?.url ?? "";
@@ -7117,10 +7265,188 @@ async function discoverAndMergeWebuiProviders(opts) {
7117
7265
  }
7118
7266
  }
7119
7267
 
7268
+ // src/server/standalone-session-identity.ts
7269
+ import * as path15 from "node:path";
7270
+ import {
7271
+ AgentStatusTracker,
7272
+ FleetNotifier,
7273
+ getSessionRegistry,
7274
+ RecoveryLock
7275
+ } from "@wrongstack/core";
7276
+ import { WebSocket as WebSocket2 } from "ws";
7277
+ async function createStandaloneSessionIdentityLifecycle(opts) {
7278
+ const { paths, events, logger } = opts;
7279
+ const registry = opts.sessionRegistry ?? getSessionRegistry(paths.globalRoot);
7280
+ const fleetNotifier = new FleetNotifier({
7281
+ baseDir: paths.globalRoot,
7282
+ projectRoot: paths.projectRoot,
7283
+ selfPid: process.pid
7284
+ });
7285
+ let activeSessionId = opts.initialSessionId;
7286
+ let stopped = false;
7287
+ let transition = Promise.resolve();
7288
+ const statusTracker = new AgentStatusTracker({
7289
+ events,
7290
+ registry,
7291
+ sessionId: () => activeSessionId,
7292
+ onUpdate: () => fleetNotifier.notify()
7293
+ });
7294
+ const register = async (sessionId) => {
7295
+ try {
7296
+ await registry.register({
7297
+ sessionId,
7298
+ projectSlug: paths.projectSlug,
7299
+ projectRoot: paths.projectRoot,
7300
+ projectName: path15.basename(paths.projectRoot),
7301
+ workingDir: opts.workingDir,
7302
+ clientType: "webui",
7303
+ pid: process.pid,
7304
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7305
+ agents: statusTracker.getAgents()
7306
+ });
7307
+ fleetNotifier.notify();
7308
+ } catch (err) {
7309
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
7310
+ }
7311
+ };
7312
+ await register(activeSessionId);
7313
+ statusTracker.start();
7314
+ const recoveryLock = opts.manageRecoveryLock === false ? void 0 : new RecoveryLock({ dir: paths.projectSessions, sessionStore: opts.sessionStore });
7315
+ let ownsRecoveryLock = false;
7316
+ if (recoveryLock) {
7317
+ try {
7318
+ await recoveryLock.write(activeSessionId);
7319
+ ownsRecoveryLock = true;
7320
+ } catch {
7321
+ }
7322
+ }
7323
+ let stopHqBridges = () => {
7324
+ };
7325
+ let closeHqPublisher = () => {
7326
+ };
7327
+ let restartHqBridges = (_sessionId) => {
7328
+ };
7329
+ if (opts.enableHqTelemetry !== false) {
7330
+ try {
7331
+ const core = await import("@wrongstack/core");
7332
+ const publisher = core.createHqPublisherFromEnv({
7333
+ clientKind: "webui",
7334
+ projectRoot: paths.projectRoot,
7335
+ projectName: path15.basename(paths.projectRoot),
7336
+ appConfig: opts.config,
7337
+ socketFactory: (url) => new WebSocket2(url)
7338
+ });
7339
+ if (publisher) {
7340
+ publisher.connect();
7341
+ closeHqPublisher = () => publisher.close();
7342
+ restartHqBridges = (sessionId) => {
7343
+ stopHqBridges();
7344
+ const stops = [];
7345
+ const add = (start) => {
7346
+ try {
7347
+ stops.push(start());
7348
+ } catch {
7349
+ }
7350
+ };
7351
+ add(
7352
+ () => core.startSessionTelemetryBridge({
7353
+ publisher,
7354
+ events,
7355
+ sessionId,
7356
+ projectRoot: paths.projectRoot,
7357
+ projectName: path15.basename(paths.projectRoot),
7358
+ globalRoot: paths.globalRoot,
7359
+ initialAgents: statusTracker.getAgents(),
7360
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
7361
+ })
7362
+ );
7363
+ add(
7364
+ () => core.startFleetTelemetryBridge({
7365
+ events,
7366
+ publisher,
7367
+ runId: sessionId,
7368
+ sessionId
7369
+ })
7370
+ );
7371
+ add(() => core.startBrainTelemetryBridge({ events, publisher, sessionId }));
7372
+ add(() => core.startWorktreeTelemetryBridge({ events, publisher, sessionId }));
7373
+ add(
7374
+ () => core.startToolTelemetryBridge({
7375
+ events,
7376
+ publisher,
7377
+ projectRoot: paths.projectRoot,
7378
+ sessionId
7379
+ })
7380
+ );
7381
+ add(() => core.startCostTelemetryBridge({ events, publisher, sessionId }));
7382
+ stopHqBridges = () => {
7383
+ for (const stop2 of stops.splice(0)) {
7384
+ try {
7385
+ stop2();
7386
+ } catch {
7387
+ }
7388
+ }
7389
+ };
7390
+ };
7391
+ restartHqBridges(activeSessionId);
7392
+ }
7393
+ } catch (err) {
7394
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
7395
+ }
7396
+ }
7397
+ const repointRecovery = async (sessionId) => {
7398
+ if (!recoveryLock) return;
7399
+ try {
7400
+ if (ownsRecoveryLock) await recoveryLock.clear();
7401
+ await recoveryLock.write(sessionId);
7402
+ ownsRecoveryLock = true;
7403
+ } catch {
7404
+ ownsRecoveryLock = false;
7405
+ }
7406
+ };
7407
+ const activate = async (sessionId) => {
7408
+ if (stopped || sessionId === activeSessionId) return;
7409
+ transition = transition.then(async () => {
7410
+ if (stopped || sessionId === activeSessionId) return;
7411
+ activeSessionId = sessionId;
7412
+ await register(sessionId);
7413
+ await repointRecovery(sessionId);
7414
+ try {
7415
+ restartHqBridges(sessionId);
7416
+ } catch (err) {
7417
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
7418
+ }
7419
+ });
7420
+ await transition;
7421
+ };
7422
+ const stop = async () => {
7423
+ if (stopped) return;
7424
+ stopped = true;
7425
+ await transition.catch(() => void 0);
7426
+ statusTracker.stop();
7427
+ stopHqBridges();
7428
+ closeHqPublisher();
7429
+ fleetNotifier.dispose();
7430
+ try {
7431
+ await registry.markClosing();
7432
+ await registry.unregister();
7433
+ } catch {
7434
+ }
7435
+ if (recoveryLock && ownsRecoveryLock) {
7436
+ await recoveryLock.clear().catch(() => void 0);
7437
+ ownsRecoveryLock = false;
7438
+ }
7439
+ };
7440
+ return { statusTracker, activate, stop };
7441
+ }
7442
+ function errorMessage2(err) {
7443
+ return err instanceof Error ? err.message : String(err);
7444
+ }
7445
+
7120
7446
  // src/server/pre-context-services.ts
7121
7447
  var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
7122
7448
  async function createPreContextServices(input) {
7123
- const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
7449
+ const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
7124
7450
  const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
7125
7451
  cacheFile: wpaths.modelsCache,
7126
7452
  ttlSeconds: 0,
@@ -7139,7 +7465,7 @@ async function createPreContextServices(input) {
7139
7465
  await discoverAndMergeWebuiProviders({
7140
7466
  config,
7141
7467
  registry: modelsRegistry,
7142
- cacheDir: path15.dirname(wpaths.modelsCache),
7468
+ cacheDir: path16.dirname(wpaths.modelsCache),
7143
7469
  logger
7144
7470
  });
7145
7471
  } catch (err) {
@@ -7182,10 +7508,23 @@ async function createPreContextServices(input) {
7182
7508
  configureDangerBypass(config.tools?.exec?.danger ?? {});
7183
7509
  configureChildEnvGitIdentity(config.git?.identity ?? null);
7184
7510
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
7185
- const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger, cacheDir: wpaths.cacheDir });
7511
+ const mcpTokenStore = new MCPVaultTokenStore(
7512
+ path16.join(wpaths.projectDir, "mcp-auth.json"),
7513
+ vault
7514
+ );
7515
+ const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
7516
+ const mcpRegistry = new MCPRegistry({
7517
+ toolRegistry,
7518
+ events,
7519
+ log: logger,
7520
+ cacheDir: wpaths.cacheDir,
7521
+ authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
7522
+ store: mcpTokenStore
7523
+ }),
7524
+ authorizationManager: mcpAuthorizationManager
7525
+ });
7186
7526
  if (config.features.mcp && config.mcpServers) {
7187
7527
  for (const [name2, cfg] of Object.entries(config.mcpServers)) {
7188
- if (cfg.enabled === false) continue;
7189
7528
  void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
7190
7529
  logger.warn(`MCP server "${name2}" failed to start at boot`, err);
7191
7530
  });
@@ -7200,7 +7539,7 @@ async function createPreContextServices(input) {
7200
7539
  // surface via the SessionRegistry.
7201
7540
  isSessionInUse: async (sessionId) => {
7202
7541
  try {
7203
- const registry = getSessionRegistry(wpaths.globalRoot);
7542
+ const registry = getSessionRegistry2(wpaths.globalRoot);
7204
7543
  const live = await registry.listByProject(wpaths.projectSlug);
7205
7544
  const hit = live.find((e) => e.sessionId === sessionId);
7206
7545
  if (hit) {
@@ -7225,120 +7564,22 @@ async function createPreContextServices(input) {
7225
7564
  await input.touchProject(projectRoot, workingDir);
7226
7565
  } catch {
7227
7566
  }
7228
- let statusTracker;
7229
- try {
7230
- const registry = getSessionRegistry(wpaths.globalRoot);
7231
- await registry.register({
7232
- sessionId: session.id,
7233
- projectSlug: wpaths.projectSlug,
7567
+ const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
7568
+ config,
7569
+ events,
7570
+ logger,
7571
+ paths: {
7572
+ globalRoot: wpaths.globalRoot,
7234
7573
  projectRoot,
7235
- projectName: path15.basename(projectRoot),
7236
- workingDir,
7237
- clientType: "webui",
7238
- pid: process.pid,
7239
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
7240
- });
7241
- const fleetNotifier = new FleetNotifier({ baseDir: wpaths.globalRoot, projectRoot, selfPid: process.pid });
7242
- statusTracker = new AgentStatusTracker({
7243
- events,
7244
- registry,
7245
- sessionId: () => session.id,
7246
- onUpdate: () => fleetNotifier.notify()
7247
- });
7248
- statusTracker.start();
7249
- let stopHqSessionBridge;
7250
- let hqTelemetryPublisher;
7251
- const stopHqAuxBridges = [];
7252
- try {
7253
- const {
7254
- createHqPublisherFromEnv,
7255
- startSessionTelemetryBridge,
7256
- startFleetTelemetryBridge,
7257
- startBrainTelemetryBridge,
7258
- startWorktreeTelemetryBridge,
7259
- startToolTelemetryBridge,
7260
- startCostTelemetryBridge
7261
- } = await import("@wrongstack/core");
7262
- const hqTelemetry = createHqPublisherFromEnv({
7263
- clientKind: "webui",
7264
- projectRoot,
7265
- projectName: path15.basename(projectRoot),
7266
- appConfig: config,
7267
- socketFactory: (url) => new WebSocket2(url)
7268
- });
7269
- if (hqTelemetry) {
7270
- hqTelemetry.connect();
7271
- hqTelemetryPublisher = hqTelemetry;
7272
- stopHqSessionBridge = startSessionTelemetryBridge({
7273
- publisher: hqTelemetry,
7274
- events,
7275
- sessionId: session.id,
7276
- projectRoot,
7277
- projectName: path15.basename(projectRoot),
7278
- globalRoot: wpaths.globalRoot,
7279
- initialAgents: statusTracker?.getAgents(),
7280
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
7281
- });
7282
- try {
7283
- stopHqAuxBridges.push(
7284
- startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
7285
- );
7286
- } catch {
7287
- }
7288
- try {
7289
- stopHqAuxBridges.push(
7290
- startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
7291
- );
7292
- } catch {
7293
- }
7294
- try {
7295
- stopHqAuxBridges.push(
7296
- startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
7297
- );
7298
- } catch {
7299
- }
7300
- try {
7301
- stopHqAuxBridges.push(
7302
- startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
7303
- );
7304
- } catch {
7305
- }
7306
- try {
7307
- stopHqAuxBridges.push(
7308
- startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
7309
- );
7310
- } catch {
7311
- }
7312
- }
7313
- } catch {
7314
- }
7315
- const stopTracking = async () => {
7316
- try {
7317
- fleetNotifier.dispose();
7318
- await registry.markClosing();
7319
- statusTracker?.stop();
7320
- stopHqSessionBridge?.();
7321
- for (const stop of stopHqAuxBridges) {
7322
- try {
7323
- stop();
7324
- } catch {
7325
- }
7326
- }
7327
- hqTelemetryPublisher?.close();
7328
- } catch {
7329
- }
7330
- };
7331
- process.once("beforeExit", () => {
7332
- void stopTracking();
7333
- });
7334
- process.once("SIGINT", () => {
7335
- void stopTracking();
7336
- });
7337
- process.once("SIGTERM", () => {
7338
- void stopTracking();
7339
- });
7340
- } catch {
7341
- }
7574
+ projectSlug: wpaths.projectSlug,
7575
+ projectSessions: wpaths.projectSessions
7576
+ },
7577
+ workingDir,
7578
+ initialSessionId: session.id,
7579
+ sessionStore,
7580
+ manageRecoveryLock: !opts.services?.session
7581
+ });
7582
+ const statusTracker = sessionIdentity.statusTracker;
7342
7583
  let context;
7343
7584
  const tokenCounter = new DefaultTokenCounter({
7344
7585
  registry: modelsRegistry,
@@ -7363,7 +7604,7 @@ async function createPreContextServices(input) {
7363
7604
  const modelCapabilitiesRef = { current: modelCapabilities };
7364
7605
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7365
7606
  const skillInstaller = config.features.skills ? new SkillInstaller({
7366
- manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
7607
+ manifestPath: path16.join(wpaths.globalRoot, "installed-skills.json"),
7367
7608
  projectSkillsDir: wpaths.inProjectSkills,
7368
7609
  globalSkillsDir: wpaths.globalSkills,
7369
7610
  projectHash: wpaths.projectHash,
@@ -7373,7 +7614,7 @@ async function createPreContextServices(input) {
7373
7614
  const bundledPromptsDir = promptsEnabled ? (() => {
7374
7615
  try {
7375
7616
  const req = createRequire2(import.meta.url);
7376
- return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7617
+ return path16.join(path16.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7377
7618
  } catch {
7378
7619
  return void 0;
7379
7620
  }
@@ -7425,6 +7666,14 @@ async function createPreContextServices(input) {
7425
7666
  const initialContextPolicy = resolveContextWindowPolicy(config.context);
7426
7667
  context.meta["contextWindowMode"] = initialContextPolicy.id;
7427
7668
  context.meta["contextWindowPolicy"] = initialContextPolicy;
7669
+ context.state.setMeta(
7670
+ "plan.path",
7671
+ sessionScopedPath(wpaths.projectSessions, session.id, ".plan.json")
7672
+ );
7673
+ context.state.setMeta(
7674
+ "task.path",
7675
+ sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
7676
+ );
7428
7677
  seedContextMeta(config, context);
7429
7678
  return {
7430
7679
  modelsRegistry,
@@ -7441,6 +7690,7 @@ async function createPreContextServices(input) {
7441
7690
  session,
7442
7691
  sessionStartedAt,
7443
7692
  statusTracker,
7693
+ sessionIdentity,
7444
7694
  tokenCounter,
7445
7695
  modeStore,
7446
7696
  modeId,
@@ -7490,6 +7740,7 @@ function patchConfig(config, updates) {
7490
7740
  }
7491
7741
 
7492
7742
  // src/server/backend-services.ts
7743
+ import { join as join13 } from "node:path";
7493
7744
  import {
7494
7745
  Agent,
7495
7746
  AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
@@ -7503,9 +7754,10 @@ import {
7503
7754
  installDesignStudioMiddleware,
7504
7755
  ObservableBrainArbiter as ObservableBrainArbiterCtor,
7505
7756
  resolveContextWindowPolicy as resolveContextWindowPolicy2,
7506
- createAutonomyBrain,
7507
- createTieredBrainArbiter,
7508
- DefaultBrainArbiter,
7757
+ BrainDecisionLedger,
7758
+ createBrainRuntime,
7759
+ resolveBrainConfigDefaults,
7760
+ EscalationRoutingBrainArbiter,
7509
7761
  GlobalMailbox as GlobalMailbox2,
7510
7762
  mailboxSessionTag,
7511
7763
  SessionMemoryConsolidator,
@@ -7519,12 +7771,13 @@ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7519
7771
  var REPLAY_LIMIT = 50;
7520
7772
  var PAUSE_TIMEOUT_MS = 6e4;
7521
7773
  var CollaborationWebSocketHandler = class {
7522
- constructor(events, logger, reader, annotations, bus) {
7774
+ constructor(events, logger, reader, annotations, bus, options = {}) {
7523
7775
  this.events = events;
7524
7776
  this.logger = logger;
7525
7777
  this.reader = reader;
7526
7778
  this.annotations = annotations;
7527
7779
  this.bus = bus;
7780
+ this.options = options;
7528
7781
  this.subscribe();
7529
7782
  this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
7530
7783
  }
@@ -7533,6 +7786,7 @@ var CollaborationWebSocketHandler = class {
7533
7786
  reader;
7534
7787
  annotations;
7535
7788
  bus;
7789
+ options;
7536
7790
  clients = /* @__PURE__ */ new Set();
7537
7791
  /** sessionId → participants currently watching it. */
7538
7792
  bySession = /* @__PURE__ */ new Map();
@@ -7560,11 +7814,16 @@ var CollaborationWebSocketHandler = class {
7560
7814
  handleMessage(ws, msg) {
7561
7815
  if (msg.type === "collab.join") {
7562
7816
  const payload = msg.payload;
7563
- if (!payload?.sessionId) {
7817
+ if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
7564
7818
  this.send(ws, this.errorMessage("collab.join requires sessionId"));
7565
7819
  return true;
7566
7820
  }
7567
- this.join(ws, payload.sessionId, payload.role ?? "observer");
7821
+ const role = payload.role ?? "observer";
7822
+ if (role !== "observer" && role !== "annotator" && role !== "controller") {
7823
+ this.send(ws, this.errorMessage(`unknown collaboration role '${String(role)}'`));
7824
+ return true;
7825
+ }
7826
+ this.join(ws, payload.sessionId, role);
7568
7827
  return true;
7569
7828
  }
7570
7829
  if (msg.type === "collab.leave") {
@@ -7599,6 +7858,20 @@ var CollaborationWebSocketHandler = class {
7599
7858
  }
7600
7859
  // ── Join / leave flow ──────────────────────────────────────────────────
7601
7860
  join(ws, sessionId, role) {
7861
+ const activeSessionId = this.options.getActiveSessionId?.();
7862
+ if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
7863
+ this.send(
7864
+ ws,
7865
+ this.errorMessage(
7866
+ `collab.join sessionId mismatch (active: ${activeSessionId})`
7867
+ )
7868
+ );
7869
+ return;
7870
+ }
7871
+ if (this.findParticipant(ws)) {
7872
+ this.send(ws, this.errorMessage("collab.join requires leaving the current session first"));
7873
+ return;
7874
+ }
7602
7875
  if (role === "controller" && !this.bus) {
7603
7876
  this.send(
7604
7877
  ws,
@@ -7617,6 +7890,13 @@ var CollaborationWebSocketHandler = class {
7617
7890
  );
7618
7891
  return;
7619
7892
  }
7893
+ if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
7894
+ this.send(
7895
+ ws,
7896
+ this.errorMessage(`role '${role}' requires explicit server authorization`)
7897
+ );
7898
+ return;
7899
+ }
7620
7900
  const participant = {
7621
7901
  participantId: randomUUID(),
7622
7902
  ws,
@@ -7870,17 +8150,13 @@ var CollaborationWebSocketHandler = class {
7870
8150
  type: "collab.event",
7871
8151
  payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
7872
8152
  };
7873
- const data = JSON.stringify(msg);
7874
- for (const bucket of this.bySession.values()) {
7875
- for (const p of bucket) {
7876
- try {
7877
- if (p.ws.readyState === 1) p.ws.send(data);
7878
- } catch (err) {
7879
- this.logger.debug?.(
7880
- `collab broadcast failed: ${toErrorMessage6(err)}`
7881
- );
7882
- }
7883
- }
8153
+ const activeSessionId = this.options.getActiveSessionId?.();
8154
+ if (activeSessionId !== void 0) {
8155
+ this.broadcast(activeSessionId, msg);
8156
+ return;
8157
+ }
8158
+ for (const sessionId of this.bySession.keys()) {
8159
+ this.broadcast(sessionId, msg);
7884
8160
  }
7885
8161
  }
7886
8162
  /**
@@ -8204,7 +8480,8 @@ var CollaborationWebSocketHandler = class {
8204
8480
  * Bus callback: a queued injection was spliced into a real tool call. Re-emit
8205
8481
  * `collab.injection.granted` with phase `'consumed'` and the now-known tool
8206
8482
  * name. The injection carries no sessionId, so resolve it from the author's
8207
- * current session; if they've already left, fall back to every live session.
8483
+ * current participant. If the author has left, fail closed rather than leak
8484
+ * the payload to unrelated sessions.
8208
8485
  */
8209
8486
  broadcastInjectionConsumed(info) {
8210
8487
  let sessionId = null;
@@ -8233,14 +8510,16 @@ var CollaborationWebSocketHandler = class {
8233
8510
  if (sessionId) {
8234
8511
  this.broadcast(sessionId, message(sessionId));
8235
8512
  } else {
8236
- for (const sid of this.bySession.keys()) this.broadcast(sid, message(sid));
8513
+ this.logger.debug?.(
8514
+ `collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
8515
+ );
8237
8516
  }
8238
8517
  }
8239
8518
  };
8240
8519
 
8241
8520
  // src/server/codebase-indexing.ts
8242
8521
  import * as fs12 from "node:fs";
8243
- import * as path16 from "node:path";
8522
+ import * as path17 from "node:path";
8244
8523
  import {
8245
8524
  cancelPendingReindexes,
8246
8525
  enqueueReindex,
@@ -8293,7 +8572,7 @@ function setupWebUICodebaseIndexing(deps2) {
8293
8572
  if (!filename) return;
8294
8573
  const rel = filename.toString();
8295
8574
  if (isIgnored(rel)) return;
8296
- const abs = path16.resolve(deps2.projectRoot, rel);
8575
+ const abs = path17.resolve(deps2.projectRoot, rel);
8297
8576
  enqueueFile(abs);
8298
8577
  });
8299
8578
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
@@ -8306,7 +8585,7 @@ function setupWebUICodebaseIndexing(deps2) {
8306
8585
  }
8307
8586
  function enqueueFile(filePath) {
8308
8587
  if (!idx.onEdit && !idx.watchExternal) return;
8309
- const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
8588
+ const abs = path17.isAbsolute(filePath) ? path17.normalize(filePath) : path17.resolve(deps2.projectRoot, filePath);
8310
8589
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
8311
8590
  enqueueReindex({
8312
8591
  projectRoot: deps2.projectRoot,
@@ -8343,9 +8622,9 @@ function isIgnored(rel) {
8343
8622
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
8344
8623
  }
8345
8624
  function isInside2(root, target) {
8346
- const normalizedRoot = path16.resolve(root);
8347
- const normalizedTarget = path16.resolve(target);
8348
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
8625
+ const normalizedRoot = path17.resolve(root);
8626
+ const normalizedTarget = path17.resolve(target);
8627
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path17.sep);
8349
8628
  }
8350
8629
 
8351
8630
  // src/server/discover-mailbox-bridge.ts
@@ -8878,21 +9157,59 @@ async function createAgentServices(input) {
8878
9157
  agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
8879
9158
  }
8880
9159
  console.log("[WebUI] Agent initialized");
8881
- const brainSettings = { maxAutoRisk: "medium" };
8882
- const autonomousBrain = {
8883
- decide: (request) => createAutonomyBrain({
8884
- provider,
8885
- model: context.model,
8886
- maxAutoRisk: "all"
8887
- // the tiered ceiling gates risk — keep inner permissive
8888
- }).decide(request)
9160
+ const brainCfg = resolveBrainConfigDefaults(config.brain, {
9161
+ fallbackModels: config.fallbackModels
9162
+ });
9163
+ const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
9164
+ let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9165
+ let brainLedger;
9166
+ const startBrainLedger = () => {
9167
+ if (brainLedger) return;
9168
+ brainLedger = new BrainDecisionLedger({ events, filePath: brainLedgerPath });
9169
+ void brainLedger.start();
9170
+ };
9171
+ if (brainLedgerEnabled) startBrainLedger();
9172
+ const brainRuntime = createBrainRuntime({
9173
+ initialConfig: brainCfg,
9174
+ defaultProviderId: config.provider,
9175
+ sessionProvider: () => provider,
9176
+ sessionModel: () => context.model,
9177
+ resolveProvider: (providerId) => {
9178
+ const savedCfg = config.providers?.[providerId] ?? {};
9179
+ return providerRegistry.create({
9180
+ ...savedCfg,
9181
+ apiKey: savedCfg.apiKey ?? config.apiKey,
9182
+ baseUrl: savedCfg.baseUrl ?? config.baseUrl,
9183
+ type: providerId
9184
+ });
9185
+ },
9186
+ ledger: {
9187
+ getPath: () => brainLedgerEnabled ? brainLedgerPath : void 0,
9188
+ isEnabled: () => brainLedgerEnabled,
9189
+ setEnabled: (on) => {
9190
+ brainLedgerEnabled = on;
9191
+ if (on) {
9192
+ startBrainLedger();
9193
+ } else {
9194
+ void brainLedger?.stop();
9195
+ brainLedger = void 0;
9196
+ }
9197
+ },
9198
+ failureStreakFor: (request) => brainLedger?.failureStreakFor(request) ?? 0,
9199
+ getDecisionDigest: (request) => brainLedger?.digestFor(request)
9200
+ },
9201
+ persist: input.persistBrainConfig
9202
+ });
9203
+ const brainSettings = {
9204
+ get maxAutoRisk() {
9205
+ return brainRuntime.getMaxAutoRisk();
9206
+ },
9207
+ set maxAutoRisk(level) {
9208
+ void brainRuntime.apply({ maxAutoRisk: level }).persisted;
9209
+ }
8889
9210
  };
8890
9211
  const brain = new ObservableBrainArbiterCtor(
8891
- createTieredBrainArbiter({
8892
- policy: new DefaultBrainArbiter(),
8893
- autonomous: autonomousBrain,
8894
- getMaxAutoRisk: () => brainSettings.maxAutoRisk
8895
- }),
9212
+ new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
8896
9213
  events
8897
9214
  );
8898
9215
  container.bind(TOKENS2.BrainArbiter, () => brain);
@@ -8932,6 +9249,12 @@ async function createAgentServices(input) {
8932
9249
  const brainMonitor = new BrainMonitor({
8933
9250
  events,
8934
9251
  brain,
9252
+ toolFailureStreak: brainCfg.monitor?.toolFailureStreak,
9253
+ errorStormCount: brainCfg.monitor?.errorStormCount,
9254
+ stallMs: brainCfg.monitor?.stallMs,
9255
+ fileChurnThreshold: brainCfg.monitor?.fileChurnThreshold,
9256
+ fileChurnWindowMs: brainCfg.monitor?.fileChurnWindowMs,
9257
+ cooldownMs: brainCfg.monitor?.cooldownMs,
8935
9258
  sessionId: () => context.session?.id,
8936
9259
  intervene: async ({ subject, body }) => {
8937
9260
  const tag = mailboxSessionTag(input.sessionGetter().id);
@@ -8996,7 +9319,10 @@ async function createAgentServices(input) {
8996
9319
  logger,
8997
9320
  input.sessionReader,
8998
9321
  input.annotationsStore,
8999
- collabBus
9322
+ collabBus,
9323
+ {
9324
+ getActiveSessionId: () => context.session.id
9325
+ }
9000
9326
  );
9001
9327
  return {
9002
9328
  collabBus,
@@ -9008,8 +9334,13 @@ async function createAgentServices(input) {
9008
9334
  pipelines,
9009
9335
  brain,
9010
9336
  brainSettings,
9337
+ brainRuntime,
9011
9338
  brainLog,
9012
9339
  brainMonitor,
9340
+ // Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
9341
+ get brainLedger() {
9342
+ return brainLedger;
9343
+ },
9013
9344
  codebaseIndexing,
9014
9345
  autoPhaseHandler,
9015
9346
  specsHandler,
@@ -9185,7 +9516,18 @@ function createConnectionHandler(opts) {
9185
9516
  }
9186
9517
 
9187
9518
  // src/server/message-dispatcher.ts
9188
- import path17 from "node:path";
9519
+ import path18 from "node:path";
9520
+ import {
9521
+ buildUserContentBlocks,
9522
+ IncomingImageError,
9523
+ parseIncomingImages
9524
+ } from "@wrongstack/core/utils";
9525
+ import {
9526
+ createToolVisionAdapters,
9527
+ ImageInputUnsupportedError,
9528
+ routeImagesForModel,
9529
+ VisionUrlBlockedError
9530
+ } from "@wrongstack/runtime/vision";
9189
9531
 
9190
9532
  // src/server/autophase-routes.ts
9191
9533
  async function handleAutoPhaseRoute(_ws, msg, handlers) {
@@ -9206,6 +9548,12 @@ async function handleBrainRoute(ws, msg, handlers) {
9206
9548
  case "brain.ask":
9207
9549
  await handlers.ask(ws, msg);
9208
9550
  return true;
9551
+ case "brain.config.get":
9552
+ await handlers.configGet(ws, msg);
9553
+ return true;
9554
+ case "brain.config.set":
9555
+ await handlers.configSet(ws, msg);
9556
+ return true;
9209
9557
  default:
9210
9558
  return false;
9211
9559
  }
@@ -9936,6 +10284,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
9936
10284
  "Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
9937
10285
  );
9938
10286
  return true;
10287
+ case "kanban.capabilities":
10288
+ ok(ws, type, { dispatchSupported: false });
10289
+ return true;
9939
10290
  case "kanban.task.remove": {
9940
10291
  const boardId = payload?.boardId;
9941
10292
  const taskId = payload?.taskId;
@@ -10015,6 +10366,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
10015
10366
  case "mailbox.purge":
10016
10367
  await handlers.purge(ws, msg);
10017
10368
  return true;
10369
+ case "mailbox.compact":
10370
+ await handlers.compact(ws, msg);
10371
+ return true;
10018
10372
  default:
10019
10373
  return false;
10020
10374
  }
@@ -10053,6 +10407,18 @@ async function handleMcpRoute(ws, msg, handlers) {
10053
10407
  case "mcp.discover":
10054
10408
  await handlers.discover(ws, msg);
10055
10409
  return true;
10410
+ case "mcp.resources":
10411
+ await handlers.resources(ws, msg);
10412
+ return true;
10413
+ case "mcp.prompts":
10414
+ await handlers.prompts(ws, msg);
10415
+ return true;
10416
+ case "mcp.resource.read":
10417
+ await handlers.resourceRead(ws, msg);
10418
+ return true;
10419
+ case "mcp.prompt.get":
10420
+ await handlers.promptGet(ws, msg);
10421
+ return true;
10056
10422
  default:
10057
10423
  return false;
10058
10424
  }
@@ -10442,7 +10808,7 @@ function createMessageDispatcher(opts) {
10442
10808
  skillLoader: deps2.skillLoader,
10443
10809
  skillInstaller: deps2.skillInstaller,
10444
10810
  projectRoot,
10445
- projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
10811
+ projectSkillsDir: path18.join(projectRoot, ".wrongstack", "skills"),
10446
10812
  globalSkillsDir: deps2.wpaths.globalSkills
10447
10813
  };
10448
10814
  }
@@ -10515,7 +10881,8 @@ function createMessageDispatcher(opts) {
10515
10881
  }
10516
10882
  case "user_message": {
10517
10883
  if (!ensureCurrentSession(ws, msg, "user_message")) return;
10518
- const content = msg.payload.content;
10884
+ const userPayload = msg.payload;
10885
+ const content = userPayload.content;
10519
10886
  if (runLock.get()) {
10520
10887
  send(ws, {
10521
10888
  type: "error",
@@ -10529,8 +10896,24 @@ function createMessageDispatcher(opts) {
10529
10896
  const thisRun = new AbortController();
10530
10897
  runLock.set(thisRun);
10531
10898
  try {
10899
+ let input = content;
10900
+ const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
10901
+ if (imageBlocks.length > 0) {
10902
+ const routed = await routeImagesForModel(
10903
+ buildUserContentBlocks(content, imageBlocks),
10904
+ {
10905
+ supportsVision: deps2.agent.ctx.provider.capabilities.vision,
10906
+ adapters: () => createToolVisionAdapters(deps2.agent.tools),
10907
+ ctx: deps2.agent.ctx,
10908
+ signal: thisRun.signal,
10909
+ providerId: deps2.agent.ctx.provider.id,
10910
+ model: deps2.agent.ctx.model
10911
+ }
10912
+ );
10913
+ input = routed.blocks;
10914
+ }
10532
10915
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
10533
- const result = await deps2.agent.run(content, {
10916
+ const result = await deps2.agent.run(input, {
10534
10917
  signal: thisRun.signal,
10535
10918
  maxIterations: maxIt
10536
10919
  });
@@ -10548,13 +10931,24 @@ function createMessageDispatcher(opts) {
10548
10931
  })
10549
10932
  });
10550
10933
  } catch (err) {
10551
- send(ws, {
10552
- type: "error",
10553
- payload: sessionPayload2({
10554
- phase: "agent.run",
10555
- message: errMessage(err)
10556
- })
10557
- });
10934
+ if (err instanceof IncomingImageError || err instanceof ImageInputUnsupportedError || err instanceof VisionUrlBlockedError) {
10935
+ send(ws, {
10936
+ type: "error",
10937
+ payload: sessionPayload2({
10938
+ phase: "user_message",
10939
+ ...err instanceof ImageInputUnsupportedError ? { code: "vision_unsupported" } : {},
10940
+ message: err.message
10941
+ })
10942
+ });
10943
+ } else {
10944
+ send(ws, {
10945
+ type: "error",
10946
+ payload: sessionPayload2({
10947
+ phase: "agent.run",
10948
+ message: errMessage(err)
10949
+ })
10950
+ });
10951
+ }
10558
10952
  } finally {
10559
10953
  if (runLock.get() === thisRun) {
10560
10954
  runLock.set(null);
@@ -10602,7 +10996,7 @@ function createMessageDispatcher(opts) {
10602
10996
  break;
10603
10997
  }
10604
10998
  case "tool.disable": {
10605
- const name2 = (msg.payload ?? {}).name;
10999
+ const name2 = msg.payload?.name;
10606
11000
  if (!name2) {
10607
11001
  send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
10608
11002
  break;
@@ -10617,7 +11011,7 @@ function createMessageDispatcher(opts) {
10617
11011
  break;
10618
11012
  }
10619
11013
  case "tool.enable": {
10620
- const name2 = (msg.payload ?? {}).name;
11014
+ const name2 = msg.payload?.name;
10621
11015
  if (!name2) {
10622
11016
  send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
10623
11017
  break;
@@ -10661,6 +11055,14 @@ function createMessageDispatcher(opts) {
10661
11055
  throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
10662
11056
  case "mcp.discover":
10663
11057
  throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
11058
+ case "mcp.resources":
11059
+ throw new Error("handleMcpRoute did not claim mcp.resources \u2014 check chain order");
11060
+ case "mcp.prompts":
11061
+ throw new Error("handleMcpRoute did not claim mcp.prompts \u2014 check chain order");
11062
+ case "mcp.resource.read":
11063
+ throw new Error("handleMcpRoute did not claim mcp.resource.read \u2014 check chain order");
11064
+ case "mcp.prompt.get":
11065
+ throw new Error("handleMcpRoute did not claim mcp.prompt.get \u2014 check chain order");
10664
11066
  // Skills — full request→response cycle lives in skills-handlers.ts.
10665
11067
  case "skills.list":
10666
11068
  await handleSkillsList(ws, makeSkillsContext());
@@ -10961,7 +11363,19 @@ var PREF_KEYS = [
10961
11363
  "favoriteModels",
10962
11364
  "favoriteModelsOnly",
10963
11365
  "modelMatrix",
10964
- "fallbackAuto"
11366
+ "fallbackAuto",
11367
+ // Refiner + TUI visual prefs (parity with the CLI's embedded server —
11368
+ // these were browser-editable there but rejected as unknown keys here).
11369
+ "refinerProvider",
11370
+ "refinerModel",
11371
+ "thinkingWord",
11372
+ "statuslineMode",
11373
+ "animationStyle",
11374
+ // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11375
+ "breakerEnabled",
11376
+ "breakerAutoKillResetMs",
11377
+ "fsAccess",
11378
+ "debugStream"
10965
11379
  ];
10966
11380
  function prefSnapshot(contextMeta) {
10967
11381
  const snapshot = {};
@@ -11032,6 +11446,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11032
11446
  setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
11033
11447
  if (typeof payload["enhanceLanguage"] === "string")
11034
11448
  setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
11449
+ if (typeof payload["refinerProvider"] === "string")
11450
+ setAutonomy("refinerProvider", payload["refinerProvider"]);
11451
+ if (typeof payload["refinerModel"] === "string")
11452
+ setAutonomy("refinerModel", payload["refinerModel"]);
11453
+ if (typeof payload["thinkingWord"] === "string")
11454
+ setAutonomy("thinkingWord", payload["thinkingWord"]);
11455
+ if (typeof payload["statuslineMode"] === "string")
11456
+ setAutonomy("statuslineMode", payload["statuslineMode"]);
11457
+ if (typeof payload["animationStyle"] === "string")
11458
+ setAutonomy("animationStyle", payload["animationStyle"]);
11035
11459
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
11036
11460
  if (typeof payload["nextPrediction"] === "boolean")
11037
11461
  decrypted.nextPrediction = payload["nextPrediction"];
@@ -11149,16 +11573,34 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11149
11573
  }
11150
11574
  decrypted.modelRuntime = mr;
11151
11575
  }
11576
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
11577
+ const cb = decrypted.circuitBreaker ?? {};
11578
+ if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
11579
+ if (typeof payload["breakerAutoKillResetMs"] === "number")
11580
+ cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
11581
+ decrypted.circuitBreaker = cb;
11582
+ }
11583
+ if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
11584
+ const restrict = payload["fsAccess"] === "project";
11585
+ const toolsCfg = decrypted.tools ?? {};
11586
+ toolsCfg.restrictToProjectRoot = restrict;
11587
+ decrypted.tools = toolsCfg;
11588
+ const featsCfg = decrypted.features ?? {};
11589
+ featsCfg.allowOutsideProjectRoot = !restrict;
11590
+ decrypted.features = featsCfg;
11591
+ }
11592
+ if (typeof payload["debugStream"] === "boolean")
11593
+ decrypted.debugStream = payload["debugStream"];
11152
11594
  }, "prefs");
11153
11595
  }
11154
11596
 
11155
11597
  // src/server/projects-manifest.ts
11156
11598
  import * as fs14 from "node:fs/promises";
11157
- import * as path18 from "node:path";
11599
+ import * as path19 from "node:path";
11158
11600
  import { projectSlug } from "@wrongstack/core";
11159
11601
  function projectsJsonPath(globalConfigPath) {
11160
- const base = path18.dirname(globalConfigPath);
11161
- return path18.join(base, "projects.json");
11602
+ const base = path19.dirname(globalConfigPath);
11603
+ return path19.join(base, "projects.json");
11162
11604
  }
11163
11605
  async function loadManifest(globalConfigPath) {
11164
11606
  try {
@@ -11171,15 +11613,15 @@ async function loadManifest(globalConfigPath) {
11171
11613
  }
11172
11614
  async function saveManifest(manifest, globalConfigPath) {
11173
11615
  const file = projectsJsonPath(globalConfigPath);
11174
- await fs14.mkdir(path18.dirname(file), { recursive: true });
11616
+ await fs14.mkdir(path19.dirname(file), { recursive: true });
11175
11617
  await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
11176
11618
  }
11177
11619
  function generateProjectSlug(rootPath) {
11178
11620
  return projectSlug(rootPath);
11179
11621
  }
11180
11622
  async function ensureProjectDataDir(slug, globalConfigPath) {
11181
- const base = path18.dirname(globalConfigPath);
11182
- const dir = path18.join(base, "projects", slug);
11623
+ const base = path19.dirname(globalConfigPath);
11624
+ const dir = path19.join(base, "projects", slug);
11183
11625
  await fs14.mkdir(dir, { recursive: true });
11184
11626
  return dir;
11185
11627
  }
@@ -11596,6 +12038,16 @@ async function handleMailboxPurge(ws, deps2, opts) {
11596
12038
  send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
11597
12039
  }
11598
12040
  }
12041
+ async function handleMailboxCompact(ws, deps2, opts) {
12042
+ try {
12043
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
12044
+ const mb = new GlobalMailbox3(dir);
12045
+ const result = await mb.autoCompact(opts);
12046
+ send(ws, { type: "mailbox.compacted", payload: result });
12047
+ } catch (err) {
12048
+ send(ws, { type: "mailbox.compacted", payload: { error: errMessage(err) } });
12049
+ }
12050
+ }
11599
12051
 
11600
12052
  // src/server/mode-handlers.ts
11601
12053
  import {
@@ -11684,7 +12136,7 @@ function createModeHandlers(ctx) {
11684
12136
  }
11685
12137
 
11686
12138
  // src/server/project-handlers.ts
11687
- import * as path19 from "node:path";
12139
+ import * as path20 from "node:path";
11688
12140
  function createProjectHandlers(ctx) {
11689
12141
  return {
11690
12142
  listProjects: async (ws) => {
@@ -11710,7 +12162,7 @@ function createProjectHandlers(ctx) {
11710
12162
  selectProject: async (ws, msg) => {
11711
12163
  const payload = msg.payload;
11712
12164
  const root = typeof payload?.root === "string" ? payload.root : "";
11713
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
12165
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
11714
12166
  send(ws, {
11715
12167
  type: "projects.selected",
11716
12168
  payload: {
@@ -11745,12 +12197,12 @@ function createProjectHandlers(ctx) {
11745
12197
  }
11746
12198
 
11747
12199
  // src/server/session-handlers.ts
11748
- import * as path20 from "node:path";
11749
12200
  import {
11750
12201
  DEFAULT_CONTEXT_WINDOW_MODE_ID,
11751
12202
  repairToolUseAdjacency,
11752
12203
  resolveContextWindowPolicy as resolveContextWindowPolicy3
11753
12204
  } from "@wrongstack/core";
12205
+ import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
11754
12206
  function createSessionHandlers(ctx) {
11755
12207
  const currentSessionId = () => ctx.getSession().id;
11756
12208
  const sessionPayload2 = (payload) => {
@@ -11776,33 +12228,46 @@ function createSessionHandlers(ctx) {
11776
12228
  });
11777
12229
  return false;
11778
12230
  };
12231
+ const finalizeSession = async (writer) => {
12232
+ await writer.append({
12233
+ type: "session_end",
12234
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12235
+ usage: ctx.tokenCounter.total()
12236
+ }).catch(() => void 0);
12237
+ await writer.close().catch(() => void 0);
12238
+ };
12239
+ const activateSession = async (next, messages, usage) => {
12240
+ const current = ctx.getSession();
12241
+ if (current !== next) await finalizeSession(current);
12242
+ ctx.setSession(next);
12243
+ ctx.context.session = next;
12244
+ ctx.context.state.replaceMessages(messages);
12245
+ ctx.context.state.replaceTodos([]);
12246
+ ctx.context.readFiles.clear();
12247
+ ctx.context.fileMtimes.clear();
12248
+ ctx.context.state.setMeta(
12249
+ "plan.path",
12250
+ sessionScopedPath2(ctx.sessionsDir, next.id, ".plan.json")
12251
+ );
12252
+ ctx.context.state.setMeta(
12253
+ "task.path",
12254
+ sessionScopedPath2(ctx.sessionsDir, next.id, ".tasks.json")
12255
+ );
12256
+ ctx.tokenCounter.reset();
12257
+ if (usage) ctx.tokenCounter.account(usage, ctx.config.model);
12258
+ ctx.setSessionStartedAt(Date.now());
12259
+ await ctx.onSessionSwapped(next.id);
12260
+ };
11779
12261
  return {
11780
12262
  newSession: async (ws, msg) => {
11781
12263
  if (!ensureCurrentSession(ws, msg, "session.new")) return;
11782
- const session = ctx.getSession();
11783
- try {
11784
- await session.append({
11785
- type: "session_end",
11786
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11787
- usage: ctx.tokenCounter.total()
11788
- });
11789
- await session.close();
11790
- } catch {
11791
- }
11792
12264
  const next = await ctx.getSessionStore().create({
11793
12265
  id: "",
11794
12266
  title: "",
11795
12267
  model: ctx.config.model,
11796
12268
  provider: ctx.config.provider
11797
12269
  });
11798
- ctx.setSession(next);
11799
- ctx.context.session = next;
11800
- ctx.context.state.replaceMessages([]);
11801
- ctx.context.state.replaceTodos([]);
11802
- ctx.context.readFiles.clear();
11803
- ctx.context.fileMtimes.clear();
11804
- ctx.tokenCounter.reset();
11805
- ctx.setSessionStartedAt(Date.now());
12270
+ await activateSession(next, []);
11806
12271
  broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
11807
12272
  },
11808
12273
  clearContext: async (ws, msg) => {
@@ -12058,23 +12523,7 @@ function createSessionHandlers(ctx) {
12058
12523
  return;
12059
12524
  }
12060
12525
  const resumed = await ctx.getSessionStore().resume(id);
12061
- try {
12062
- await current.append({
12063
- type: "session_end",
12064
- ts: (/* @__PURE__ */ new Date()).toISOString(),
12065
- usage: ctx.tokenCounter.total()
12066
- });
12067
- await current.close();
12068
- } catch {
12069
- }
12070
- ctx.setSession(resumed.writer);
12071
- ctx.context.session = resumed.writer;
12072
- ctx.context.state.replaceMessages(resumed.data.messages);
12073
- ctx.context.readFiles.clear();
12074
- ctx.context.fileMtimes.clear();
12075
- ctx.tokenCounter.reset();
12076
- ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
12077
- ctx.setSessionStartedAt(Date.now());
12526
+ await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
12078
12527
  broadcast(ctx.clients, {
12079
12528
  type: "session.start",
12080
12529
  payload: {
@@ -12098,10 +12547,7 @@ function createSessionHandlers(ctx) {
12098
12547
  try {
12099
12548
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
12100
12549
  const projectRoot = ctx.getProjectRoot();
12101
- const rewinder = new DefaultSessionRewinder(
12102
- path20.join(projectRoot, ".wrongstack", "sessions"),
12103
- projectRoot
12104
- );
12550
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12105
12551
  const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
12106
12552
  send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
12107
12553
  } catch {
@@ -12114,10 +12560,7 @@ function createSessionHandlers(ctx) {
12114
12560
  try {
12115
12561
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
12116
12562
  const projectRoot = ctx.getProjectRoot();
12117
- const rewinder = new DefaultSessionRewinder(
12118
- path20.join(projectRoot, ".wrongstack", "sessions"),
12119
- projectRoot
12120
- );
12563
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12121
12564
  await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
12122
12565
  await ctx.context.session.truncateToCheckpoint(checkpointIndex);
12123
12566
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
@@ -12333,8 +12776,10 @@ function buildRoutes(state, deps2, cb) {
12333
12776
  getProjectRoot: state.getProjectRoot,
12334
12777
  getSession: state.getSession,
12335
12778
  getSessionStore: state.getSessionStore,
12779
+ sessionsDir: deps2.wpaths.projectSessions,
12336
12780
  setSession: state.setSession,
12337
12781
  setSessionStartedAt: state.setSessionStartedAt,
12782
+ onSessionSwapped: cb.onSessionSwapped,
12338
12783
  sessionStartPayload: cb.sessionStartPayload
12339
12784
  });
12340
12785
  const projectRoutes = createProjectHandlers({
@@ -12429,6 +12874,17 @@ function buildRoutes(state, deps2, cb) {
12429
12874
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
12430
12875
  }
12431
12876
  }
12877
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
12878
+ const { getProcessRegistry } = await import("@wrongstack/tools");
12879
+ getProcessRegistry().setBreakerConfig({
12880
+ ...typeof payload["breakerEnabled"] === "boolean" ? { enabled: payload["breakerEnabled"] } : {},
12881
+ ...typeof payload["breakerAutoKillResetMs"] === "number" ? { autoKillResetMs: payload["breakerAutoKillResetMs"] } : {}
12882
+ });
12883
+ }
12884
+ if (typeof payload["debugStream"] === "boolean") {
12885
+ const { setDebugStreamEnabled } = await import("@wrongstack/providers");
12886
+ setDebugStreamEnabled(payload["debugStream"]);
12887
+ }
12432
12888
  if (typeof payload["logLevel"] === "string") {
12433
12889
  const valid = ["debug", "info", "warn", "error"];
12434
12890
  if (valid.includes(payload["logLevel"])) {
@@ -12506,6 +12962,13 @@ function buildRoutes(state, deps2, cb) {
12506
12962
  { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
12507
12963
  parsed.value
12508
12964
  );
12965
+ },
12966
+ compact: (ws, msg) => {
12967
+ return handleMailboxCompact(
12968
+ ws,
12969
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
12970
+ msg.payload ?? {}
12971
+ );
12509
12972
  }
12510
12973
  };
12511
12974
  const mcpRoutes = {
@@ -12518,14 +12981,29 @@ function buildRoutes(state, deps2, cb) {
12518
12981
  sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12519
12982
  wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12520
12983
  restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12521
- discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
12984
+ discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12985
+ resources: (ws, msg) => handleMcpResources(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12986
+ prompts: (ws, msg) => handleMcpPrompts(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12987
+ resourceRead: (ws, msg) => handleMcpResourceRead(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12988
+ promptGet: (ws, msg) => handleMcpPromptGet(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
12989
+ };
12990
+ const sendBrainStatus = (ws) => {
12991
+ const snapshot = deps2.brainRuntime.getSnapshot();
12992
+ send(ws, {
12993
+ type: "brain.status",
12994
+ payload: {
12995
+ maxAutoRisk: deps2.brainSettings.maxAutoRisk,
12996
+ log: deps2.brainLog,
12997
+ mode: snapshot.mode,
12998
+ poolLabels: snapshot.poolLabels,
12999
+ councilLabels: snapshot.councilLabels,
13000
+ ledgerPath: snapshot.ledger.path
13001
+ }
13002
+ });
12522
13003
  };
12523
13004
  const brainRoutes = {
12524
13005
  status: (ws) => {
12525
- send(ws, {
12526
- type: "brain.status",
12527
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
12528
- });
13006
+ sendBrainStatus(ws);
12529
13007
  },
12530
13008
  risk: (ws, msg) => {
12531
13009
  const parsed = validateBrainRiskPayload(msg.payload);
@@ -12535,11 +13013,43 @@ function buildRoutes(state, deps2, cb) {
12535
13013
  }
12536
13014
  const { level } = parsed.value;
12537
13015
  deps2.brainSettings.maxAutoRisk = level;
13016
+ sendBrainStatus(ws);
13017
+ },
13018
+ configGet: (ws) => {
12538
13019
  send(ws, {
12539
- type: "brain.status",
12540
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
13020
+ type: "brain.config",
13021
+ payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
12541
13022
  });
12542
13023
  },
13024
+ configSet: async (ws, msg) => {
13025
+ const parsed = validateBrainConfigSetPayload(msg.payload);
13026
+ if (!parsed.ok) {
13027
+ sendResult(ws, false, parsed.message);
13028
+ return;
13029
+ }
13030
+ try {
13031
+ const { persisted } = deps2.brainRuntime.apply(parsed.value.patch);
13032
+ const result = await persisted;
13033
+ send(ws, {
13034
+ type: "brain.config",
13035
+ payload: {
13036
+ config: deps2.brainRuntime.getSnapshot(),
13037
+ persisted: result.ok,
13038
+ ...result.ok ? {} : { error: result.error ?? "Persist failed." }
13039
+ }
13040
+ });
13041
+ sendBrainStatus(ws);
13042
+ } catch (err) {
13043
+ send(ws, {
13044
+ type: "brain.config",
13045
+ payload: {
13046
+ config: deps2.brainRuntime.getSnapshot(),
13047
+ persisted: false,
13048
+ error: `Invalid Brain setting: ${errMessage(err)}`
13049
+ }
13050
+ });
13051
+ }
13052
+ },
12543
13053
  ask: async (ws, msg) => {
12544
13054
  const parsed = validateBrainAskPayload(msg.payload);
12545
13055
  if (!parsed.ok) {
@@ -12647,7 +13157,8 @@ async function startWebUI(opts = {}) {
12647
13157
  promptsCtx,
12648
13158
  modelCapabilitiesRef,
12649
13159
  provider,
12650
- context
13160
+ context,
13161
+ sessionIdentity
12651
13162
  } = preContext;
12652
13163
  let sessionStore = preContext.sessionStore;
12653
13164
  let session = preContext.session;
@@ -12680,7 +13191,12 @@ async function startWebUI(opts = {}) {
12680
13191
  modelCapabilitiesRef,
12681
13192
  sessionGetter: () => session,
12682
13193
  sessionReader,
12683
- annotationsStore
13194
+ annotationsStore,
13195
+ // Brain settings persist to the GLOBAL config only (config.brain is on
13196
+ // the in-project deny list), serialized behind the shared write lock.
13197
+ persistBrainConfig: (brainConfig) => updateGlobalConfig2((decrypted) => {
13198
+ decrypted["brain"] = brainConfig;
13199
+ }, "brain.config")
12684
13200
  });
12685
13201
  const {
12686
13202
  compactor,
@@ -12690,6 +13206,7 @@ async function startWebUI(opts = {}) {
12690
13206
  pipelines,
12691
13207
  brain,
12692
13208
  brainSettings,
13209
+ brainRuntime,
12693
13210
  brainLog,
12694
13211
  brainMonitor,
12695
13212
  codebaseIndexing,
@@ -12860,10 +13377,12 @@ async function startWebUI(opts = {}) {
12860
13377
  terminalHandler,
12861
13378
  brain,
12862
13379
  brainSettings,
13380
+ brainRuntime,
12863
13381
  brainLog
12864
13382
  };
12865
13383
  const cb = {
12866
13384
  sessionStartPayload,
13385
+ onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
12867
13386
  updateAutoCompactionMaxContext,
12868
13387
  updateGlobalConfig: updateGlobalConfig2,
12869
13388
  persistPrefsToConfig: persistPrefsToConfig2,
@@ -12989,7 +13508,10 @@ async function startWebUI(opts = {}) {
12989
13508
  onShutdown: async () => {
12990
13509
  credentialWatcherClose?.();
12991
13510
  brainMonitor.stop();
13511
+ await agentServices.brainLedger?.stop().catch(() => {
13512
+ });
12992
13513
  await mcpRegistry.stopAll().catch(() => void 0);
13514
+ await sessionIdentity.stop();
12993
13515
  eventArming.getDispose()?.();
12994
13516
  if (eternalSubscription) {
12995
13517
  eternalSubscription.dispose();
@@ -13166,7 +13688,11 @@ export {
13166
13688
  handleMcpDiscover,
13167
13689
  handleMcpEnable,
13168
13690
  handleMcpList,
13691
+ handleMcpPromptGet,
13692
+ handleMcpPrompts,
13169
13693
  handleMcpRemove,
13694
+ handleMcpResourceRead,
13695
+ handleMcpResources,
13170
13696
  handleMcpRestart,
13171
13697
  handleMcpRoute,
13172
13698
  handleMcpSleep,