@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
@@ -2992,7 +2992,7 @@ function mapStatus(raw) {
2992
2992
  return "stopped";
2993
2993
  }
2994
2994
  }
2995
- function toView(info) {
2995
+ function toView(info, health) {
2996
2996
  const view = {
2997
2997
  name: info.name,
2998
2998
  transport: info.transport,
@@ -3004,6 +3004,7 @@ function toView(info) {
3004
3004
  };
3005
3005
  if (info.description !== void 0) view.description = info.description;
3006
3006
  if (info.lazy !== void 0) view.lazy = info.lazy;
3007
+ if (health !== void 0) view.health = health;
3007
3008
  return view;
3008
3009
  }
3009
3010
  function deps(ws, globalConfigPath, registry) {
@@ -3029,7 +3030,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
3029
3030
  registry: mcpRegistry,
3030
3031
  presets: allServers()
3031
3032
  });
3032
- send(ws, { type: "mcp.list", payload: { servers: servers.map(toView) } });
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
+ });
3033
3040
  }
3034
3041
  async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
3035
3042
  const d = deps(ws, globalConfigPath, mcpRegistry);
@@ -3179,6 +3186,99 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
3179
3186
  payload: { success: result.ok, message: result.message }
3180
3187
  });
3181
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
+ }
3182
3282
 
3183
3283
  // src/server/memory-handlers.ts
3184
3284
  async function handleMemoryList(ws, memoryStore) {
@@ -4254,6 +4354,16 @@ function validateBrainAskPayload(payload) {
4254
4354
  }
4255
4355
  return { ok: true, value: { question: question.trim() } };
4256
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
+ }
4257
4367
  function validateAutonomySwitchPayload(payload) {
4258
4368
  if (!isRecord(payload)) {
4259
4369
  return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
@@ -4315,7 +4425,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4315
4425
  "hqEnabled",
4316
4426
  "hqRawContent",
4317
4427
  "fallbackAuto",
4318
- "favoriteModelsOnly"
4428
+ "favoriteModelsOnly",
4429
+ "breakerEnabled",
4430
+ "debugStream"
4319
4431
  ]);
4320
4432
  var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4321
4433
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
@@ -4326,9 +4438,17 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4326
4438
  "maxIterations",
4327
4439
  "maxConcurrent",
4328
4440
  "enhanceDelayMs",
4329
- "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"
4330
4451
  ]);
4331
- var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
4332
4452
  var ENUM_PREF_KEYS = {
4333
4453
  autonomy: AUTONOMY_VALUES,
4334
4454
  contextStrategy: CONTEXT_STRATEGY_VALUES,
@@ -4339,7 +4459,10 @@ var ENUM_PREF_KEYS = {
4339
4459
  auditLevel: AUDIT_LEVEL_VALUES,
4340
4460
  reasoningMode: REASONING_MODE_VALUES,
4341
4461
  reasoningEffort: REASONING_EFFORT_VALUES,
4342
- 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"])
4343
4466
  };
4344
4467
  function validateModelRuntimeValue(modelRuntime, path22) {
4345
4468
  const reasoning = modelRuntime["reasoning"];
@@ -5662,6 +5785,10 @@ function shouldLogWatcherStats() {
5662
5785
  function setupEvents(deps2) {
5663
5786
  const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5664
5787
  const disposers = [];
5788
+ let disposed = false;
5789
+ const on = (event, listener) => {
5790
+ disposers.push(events.on(event, listener));
5791
+ };
5665
5792
  const currentSessionId = () => context.session?.id ?? "";
5666
5793
  const sessionPayload2 = (payload) => {
5667
5794
  const provided = payload["sessionId"];
@@ -5677,20 +5804,20 @@ function setupEvents(deps2) {
5677
5804
  sessionBridge?.append(event).catch(() => {
5678
5805
  });
5679
5806
  };
5680
- events.on("iteration.started", (e) => {
5807
+ on("iteration.started", (e) => {
5681
5808
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
5682
5809
  broadcast2(clients, {
5683
5810
  type: "iteration.started",
5684
5811
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
5685
5812
  });
5686
5813
  });
5687
- events.on("iteration.completed", (e) => {
5814
+ on("iteration.completed", (e) => {
5688
5815
  broadcast2(clients, {
5689
5816
  type: "iteration.completed",
5690
5817
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
5691
5818
  });
5692
5819
  });
5693
- events.on("iteration.limit_reached", (e) => {
5820
+ on("iteration.limit_reached", (e) => {
5694
5821
  broadcast2(clients, {
5695
5822
  type: "iteration.limit_reached",
5696
5823
  payload: sessionPayload2({
@@ -5700,19 +5827,19 @@ function setupEvents(deps2) {
5700
5827
  })
5701
5828
  });
5702
5829
  });
5703
- events.on("provider.text_delta", (e) => {
5830
+ on("provider.text_delta", (e) => {
5704
5831
  broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5705
5832
  });
5706
- events.on("provider.thinking_delta", (e) => {
5833
+ on("provider.thinking_delta", (e) => {
5707
5834
  broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5708
5835
  });
5709
- events.on("provider.stream_error", (e) => {
5836
+ on("provider.stream_error", (e) => {
5710
5837
  broadcast2(clients, {
5711
5838
  type: "provider.stream_error",
5712
5839
  payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
5713
5840
  });
5714
5841
  });
5715
- events.on("tool.started", (e) => {
5842
+ on("tool.started", (e) => {
5716
5843
  broadcast2(clients, {
5717
5844
  type: "tool.started",
5718
5845
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
@@ -5725,7 +5852,7 @@ function setupEvents(deps2) {
5725
5852
  input: e.input
5726
5853
  });
5727
5854
  });
5728
- events.on("tool.progress", (e) => {
5855
+ on("tool.progress", (e) => {
5729
5856
  broadcast2(clients, {
5730
5857
  type: "tool.progress",
5731
5858
  // Nested `event` shape — the client handler reads `payload.event?.text`
@@ -5746,7 +5873,7 @@ function setupEvents(deps2) {
5746
5873
  }
5747
5874
  });
5748
5875
  });
5749
- events.on("tool.executed", (e) => {
5876
+ on("tool.executed", (e) => {
5750
5877
  broadcast2(clients, {
5751
5878
  type: "tool.executed",
5752
5879
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
@@ -5804,7 +5931,7 @@ function setupEvents(deps2) {
5804
5931
  })();
5805
5932
  }
5806
5933
  });
5807
- events.on("tool.loop_detected", (e) => {
5934
+ on("tool.loop_detected", (e) => {
5808
5935
  broadcast2(clients, {
5809
5936
  type: "tool.loop_detected",
5810
5937
  payload: sessionPayload2({
@@ -5816,19 +5943,19 @@ function setupEvents(deps2) {
5816
5943
  })
5817
5944
  });
5818
5945
  });
5819
- events.on("trust.persisted", (e) => {
5946
+ on("trust.persisted", (e) => {
5820
5947
  broadcast2(clients, {
5821
5948
  type: "trust.persisted",
5822
5949
  payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
5823
5950
  });
5824
5951
  });
5825
- events.on("delegate.started", (e) => {
5952
+ on("delegate.started", (e) => {
5826
5953
  broadcast2(clients, {
5827
5954
  type: "delegate.started",
5828
5955
  payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
5829
5956
  });
5830
5957
  });
5831
- events.on("delegate.completed", (e) => {
5958
+ on("delegate.completed", (e) => {
5832
5959
  broadcast2(clients, {
5833
5960
  type: "delegate.completed",
5834
5961
  payload: sessionPayload2({
@@ -5846,7 +5973,7 @@ function setupEvents(deps2) {
5846
5973
  })
5847
5974
  });
5848
5975
  });
5849
- events.on("provider.response", (e) => {
5976
+ on("provider.response", (e) => {
5850
5977
  broadcast2(clients, {
5851
5978
  type: "provider.response",
5852
5979
  payload: sessionPayload2({
@@ -5858,7 +5985,7 @@ function setupEvents(deps2) {
5858
5985
  })
5859
5986
  });
5860
5987
  });
5861
- events.on("ctx.pct", (e) => {
5988
+ on("ctx.pct", (e) => {
5862
5989
  broadcast2(clients, {
5863
5990
  type: "ctx.pct",
5864
5991
  payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
@@ -5875,28 +6002,28 @@ function setupEvents(deps2) {
5875
6002
  })
5876
6003
  });
5877
6004
  });
5878
- events.on("ctx.max_context", (e) => {
6005
+ on("ctx.max_context", (e) => {
5879
6006
  broadcast2(clients, {
5880
6007
  type: "ctx.max_context",
5881
6008
  payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
5882
6009
  });
5883
6010
  });
5884
- events.on("token.threshold", (e) => {
6011
+ on("token.threshold", (e) => {
5885
6012
  broadcast2(clients, {
5886
6013
  type: "token.threshold",
5887
6014
  payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
5888
6015
  });
5889
6016
  });
5890
- events.on("token.cost_estimate_unavailable", (e) => {
6017
+ on("token.cost_estimate_unavailable", (e) => {
5891
6018
  broadcast2(clients, {
5892
6019
  type: "token.cost_estimate_unavailable",
5893
6020
  payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
5894
6021
  });
5895
6022
  });
5896
- events.on("context.repaired", (e) => {
6023
+ on("context.repaired", (e) => {
5897
6024
  broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5898
6025
  });
5899
- events.on("tool.confirm_needed", (e) => {
6026
+ on("tool.confirm_needed", (e) => {
5900
6027
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
5901
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 });
5902
6029
  pendingConfirms.set(id, {
@@ -5907,7 +6034,7 @@ function setupEvents(deps2) {
5907
6034
  });
5908
6035
  broadcast2(clients, { type: "tool.confirm_needed", payload });
5909
6036
  });
5910
- events.on("error", (e) => {
6037
+ on("error", (e) => {
5911
6038
  broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5912
6039
  appendForCurrentSession(e.sessionId, {
5913
6040
  type: "error",
@@ -5916,13 +6043,13 @@ function setupEvents(deps2) {
5916
6043
  phase: e.phase
5917
6044
  });
5918
6045
  });
5919
- events.on("session.damaged", (e) => {
6046
+ on("session.damaged", (e) => {
5920
6047
  broadcast2(clients, {
5921
6048
  type: "session.damaged",
5922
6049
  payload: { sessionId: e.sessionId, detail: e.detail }
5923
6050
  });
5924
6051
  });
5925
- events.on("session.rewound", (e) => {
6052
+ on("session.rewound", (e) => {
5926
6053
  broadcast2(clients, {
5927
6054
  type: "session.rewound",
5928
6055
  payload: sessionPayload2({
@@ -5933,7 +6060,7 @@ function setupEvents(deps2) {
5933
6060
  })
5934
6061
  });
5935
6062
  });
5936
- events.on("checkpoint.written", (e) => {
6063
+ on("checkpoint.written", (e) => {
5937
6064
  broadcast2(clients, {
5938
6065
  type: "checkpoint.written",
5939
6066
  payload: sessionPayload2({
@@ -5945,19 +6072,19 @@ function setupEvents(deps2) {
5945
6072
  })
5946
6073
  });
5947
6074
  });
5948
- events.on("in_flight.started", (e) => {
6075
+ on("in_flight.started", (e) => {
5949
6076
  broadcast2(clients, {
5950
6077
  type: "in_flight.started",
5951
6078
  payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
5952
6079
  });
5953
6080
  });
5954
- events.on("in_flight.ended", (e) => {
6081
+ on("in_flight.ended", (e) => {
5955
6082
  broadcast2(clients, {
5956
6083
  type: "in_flight.ended",
5957
6084
  payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
5958
6085
  });
5959
6086
  });
5960
- events.on("provider.retry", (e) => {
6087
+ on("provider.retry", (e) => {
5961
6088
  broadcast2(clients, {
5962
6089
  type: "provider.retry",
5963
6090
  payload: sessionPayload2({
@@ -5979,7 +6106,7 @@ function setupEvents(deps2) {
5979
6106
  description: e.description
5980
6107
  });
5981
6108
  });
5982
- events.on("provider.error", (e) => {
6109
+ on("provider.error", (e) => {
5983
6110
  broadcast2(clients, {
5984
6111
  type: "provider.error",
5985
6112
  payload: sessionPayload2({
@@ -5999,7 +6126,7 @@ function setupEvents(deps2) {
5999
6126
  retryable: e.retryable
6000
6127
  });
6001
6128
  });
6002
- events.on("provider.fallback", (e) => {
6129
+ on("provider.fallback", (e) => {
6003
6130
  broadcast2(clients, {
6004
6131
  type: "provider.fallback",
6005
6132
  payload: sessionPayload2({
@@ -6011,7 +6138,7 @@ function setupEvents(deps2) {
6011
6138
  })
6012
6139
  });
6013
6140
  });
6014
- events.on("compaction.fired", (e) => {
6141
+ on("compaction.fired", (e) => {
6015
6142
  broadcast2(clients, {
6016
6143
  type: "context.compacted",
6017
6144
  payload: sessionPayload2({
@@ -6023,7 +6150,7 @@ function setupEvents(deps2) {
6023
6150
  })
6024
6151
  });
6025
6152
  });
6026
- events.on("compaction.failed", (e) => {
6153
+ on("compaction.failed", (e) => {
6027
6154
  broadcast2(clients, {
6028
6155
  type: "compaction.failed",
6029
6156
  payload: sessionPayload2({
@@ -6038,25 +6165,25 @@ function setupEvents(deps2) {
6038
6165
  })
6039
6166
  });
6040
6167
  });
6041
- events.on("mcp.server.connected", (e) => {
6168
+ on("mcp.server.connected", (e) => {
6042
6169
  broadcast2(clients, {
6043
6170
  type: "mcp.server.connected",
6044
6171
  payload: { name: e.name, toolCount: e.toolCount }
6045
6172
  });
6046
6173
  });
6047
- events.on("mcp.server.reconnected", (e) => {
6174
+ on("mcp.server.reconnected", (e) => {
6048
6175
  broadcast2(clients, {
6049
6176
  type: "mcp.server.reconnected",
6050
6177
  payload: { name: e.name, toolCount: e.toolCount }
6051
6178
  });
6052
6179
  });
6053
- events.on("mcp.server.disconnected", (e) => {
6180
+ on("mcp.server.disconnected", (e) => {
6054
6181
  broadcast2(clients, {
6055
6182
  type: "mcp.server.disconnected",
6056
6183
  payload: { name: e.name, reason: e.reason }
6057
6184
  });
6058
6185
  });
6059
- events.on("coordinator.stats", (e) => {
6186
+ on("coordinator.stats", (e) => {
6060
6187
  broadcast2(clients, {
6061
6188
  type: "coordinator.stats",
6062
6189
  payload: sessionPayload2({
@@ -6084,16 +6211,16 @@ function setupEvents(deps2) {
6084
6211
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
6085
6212
  });
6086
6213
  const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
6087
- 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 }));
6088
- events.on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6089
- events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6090
- 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 }));
6091
- events.on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6092
- events.on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6093
- events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6094
- 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 }));
6095
- events.on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
6096
- events.on("agent.timeline.message", (e) => {
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) => {
6097
6224
  broadcast2(clients, {
6098
6225
  type: "agent.timeline.message",
6099
6226
  payload: sessionPayload2({
@@ -6109,7 +6236,7 @@ function setupEvents(deps2) {
6109
6236
  })
6110
6237
  });
6111
6238
  });
6112
- events.on("agent.status_changed", (e) => {
6239
+ on("agent.status_changed", (e) => {
6113
6240
  broadcast2(clients, {
6114
6241
  type: "agent.status_changed",
6115
6242
  payload: sessionPayload2({
@@ -6124,7 +6251,7 @@ function setupEvents(deps2) {
6124
6251
  });
6125
6252
  });
6126
6253
  let leaderSpawned = false;
6127
- events.on("iteration.started", (e) => {
6254
+ on("iteration.started", (e) => {
6128
6255
  if (!leaderSpawned) {
6129
6256
  leaderSpawned = true;
6130
6257
  const provider = context.provider?.id ?? "unknown";
@@ -6138,7 +6265,7 @@ function setupEvents(deps2) {
6138
6265
  });
6139
6266
  }
6140
6267
  });
6141
- events.on("tool.executed", (e) => {
6268
+ on("tool.executed", (e) => {
6142
6269
  forwardSubagent("tool_executed", {
6143
6270
  sessionId: e.sessionId,
6144
6271
  subagentId: "leader",
@@ -6147,7 +6274,7 @@ function setupEvents(deps2) {
6147
6274
  ok: e.ok
6148
6275
  });
6149
6276
  });
6150
- events.on("provider.response", (e) => {
6277
+ on("provider.response", (e) => {
6151
6278
  if (e.usage?.input != null) {
6152
6279
  const maxCtx = context.provider.capabilities.maxContext;
6153
6280
  const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
@@ -6164,7 +6291,7 @@ function setupEvents(deps2) {
6164
6291
  });
6165
6292
  }
6166
6293
  });
6167
- events.on("iteration.completed", (e) => {
6294
+ on("iteration.completed", (e) => {
6168
6295
  if (!leaderSpawned) {
6169
6296
  leaderSpawned = true;
6170
6297
  const provider = context.provider?.id ?? "unknown";
@@ -6196,7 +6323,7 @@ function setupEvents(deps2) {
6196
6323
  payload: sessionPayload2({ event: eventName, ...payload })
6197
6324
  });
6198
6325
  });
6199
- events.on("client.status", async (e) => {
6326
+ on("client.status", async (e) => {
6200
6327
  broadcast2(clients, { type: "client.status_update", payload: e });
6201
6328
  if (wpaths?.projectStatus) {
6202
6329
  try {
@@ -6282,6 +6409,7 @@ function setupEvents(deps2) {
6282
6409
  const startWatcher = async () => {
6283
6410
  try {
6284
6411
  await fs10.mkdir(projectsDir, { recursive: true });
6412
+ if (disposed) return;
6285
6413
  watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6286
6414
  if (eventType !== "change" && eventType !== "rename") return;
6287
6415
  if (filename == null) return;
@@ -6312,7 +6440,7 @@ function setupEvents(deps2) {
6312
6440
  );
6313
6441
  }
6314
6442
  };
6315
- events.on("client.status", (e) => {
6443
+ on("client.status", (e) => {
6316
6444
  if (e.projectHash) {
6317
6445
  const hash = String(e.projectHash);
6318
6446
  if (!knownProjectHashes.has(hash)) {
@@ -6352,7 +6480,9 @@ function setupEvents(deps2) {
6352
6480
  const registry = new SessionRegistry(globalRoot);
6353
6481
  const sessions = await registry.list();
6354
6482
  const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
6355
- const live = sessions.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6483
+ const live = sessions.filter(
6484
+ (s) => s.status === "active" || s.status === "idle"
6485
+ ).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6356
6486
  sessionId: s.sessionId,
6357
6487
  projectName: s.projectName,
6358
6488
  projectSlug: s.projectSlug,
@@ -6406,6 +6536,8 @@ function setupEvents(deps2) {
6406
6536
  void broadcastSessions();
6407
6537
  }
6408
6538
  return () => {
6539
+ if (disposed) return;
6540
+ disposed = true;
6409
6541
  for (const dispose of disposers) {
6410
6542
  try {
6411
6543
  dispose();
@@ -6632,7 +6764,7 @@ function createWsServers(ports, accessToken) {
6632
6764
  allowedHostnames: publicHostnames,
6633
6765
  allowBrowserUrlToken: Boolean(ports.publicWsUrl)
6634
6766
  });
6635
- const WS_MAX_PAYLOAD = 8 * 1024 * 1024;
6767
+ const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
6636
6768
  const wssPrimary = new WebSocketServer({
6637
6769
  port: ports.wsPort,
6638
6770
  host: ports.wsHost,
@@ -6740,12 +6872,10 @@ function registerShutdown(deps2) {
6740
6872
  }
6741
6873
 
6742
6874
  // src/server/pre-context-services.ts
6743
- import * as path14 from "node:path";
6875
+ import * as path15 from "node:path";
6744
6876
  import { createRequire as createRequire2 } from "node:module";
6745
- import { WebSocket as WebSocket2 } from "ws";
6746
6877
  import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
6747
6878
  import {
6748
- AgentStatusTracker,
6749
6879
  AnnotationsStore,
6750
6880
  DEFAULT_SESSION_PRUNE_DAYS,
6751
6881
  DefaultModelsRegistry,
@@ -6756,7 +6886,6 @@ import {
6756
6886
  DefaultSkillLoader,
6757
6887
  DefaultSystemPromptBuilder,
6758
6888
  EventBus,
6759
- FleetNotifier,
6760
6889
  GlobalMailbox,
6761
6890
  PromptUsageStore,
6762
6891
  ProviderRegistry,
@@ -6767,8 +6896,8 @@ import {
6767
6896
  applyToolDescriptionModes,
6768
6897
  applyToolResultRenderModes,
6769
6898
  configureChildEnvGitIdentity,
6899
+ getSessionRegistry as getSessionRegistry2,
6770
6900
  resolveContextWindowPolicy,
6771
- getSessionRegistry,
6772
6901
  makeMailboxTool,
6773
6902
  makeMailInboxTool,
6774
6903
  makeFleetStatusTool,
@@ -6777,7 +6906,12 @@ import {
6777
6906
  import {
6778
6907
  createSuperMemoryTools
6779
6908
  } from "@wrongstack/super-memory";
6780
- import { MCPRegistry } from "@wrongstack/mcp";
6909
+ import {
6910
+ MCPAuthorizationManager,
6911
+ MCPRegistry,
6912
+ MCPVaultTokenStore,
6913
+ createVaultBackedMcpAuthorizationProviderFactory
6914
+ } from "@wrongstack/mcp";
6781
6915
  import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
6782
6916
  import { createDefaultContainer } from "@wrongstack/runtime";
6783
6917
  import {
@@ -6789,7 +6923,7 @@ import {
6789
6923
  rememberTool,
6790
6924
  searchMemoryTool
6791
6925
  } from "@wrongstack/tools";
6792
- import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6926
+ import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6793
6927
 
6794
6928
  // src/server/setup-screen.ts
6795
6929
  import { expectDefined as expectDefined2 } from "@wrongstack/core";
@@ -6927,6 +7061,20 @@ function seedContextMeta(config, context) {
6927
7061
  meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
6928
7062
  meta["cacheTtl"] = mr.cache?.ttl ?? "default";
6929
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;
6930
7078
  const hqConfig = config.hq;
6931
7079
  meta["hqEnabled"] = hqConfig?.enabled === true;
6932
7080
  meta["hqUrl"] = hqConfig?.url ?? "";
@@ -7023,10 +7171,188 @@ async function discoverAndMergeWebuiProviders(opts) {
7023
7171
  }
7024
7172
  }
7025
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
+
7026
7352
  // src/server/pre-context-services.ts
7027
7353
  var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
7028
7354
  async function createPreContextServices(input) {
7029
- const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
7355
+ const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
7030
7356
  const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
7031
7357
  cacheFile: wpaths.modelsCache,
7032
7358
  ttlSeconds: 0,
@@ -7045,7 +7371,7 @@ async function createPreContextServices(input) {
7045
7371
  await discoverAndMergeWebuiProviders({
7046
7372
  config,
7047
7373
  registry: modelsRegistry,
7048
- cacheDir: path14.dirname(wpaths.modelsCache),
7374
+ cacheDir: path15.dirname(wpaths.modelsCache),
7049
7375
  logger
7050
7376
  });
7051
7377
  } catch (err) {
@@ -7088,10 +7414,23 @@ async function createPreContextServices(input) {
7088
7414
  configureDangerBypass(config.tools?.exec?.danger ?? {});
7089
7415
  configureChildEnvGitIdentity(config.git?.identity ?? null);
7090
7416
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
7091
- const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger, cacheDir: wpaths.cacheDir });
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
+ });
7092
7432
  if (config.features.mcp && config.mcpServers) {
7093
7433
  for (const [name2, cfg] of Object.entries(config.mcpServers)) {
7094
- if (cfg.enabled === false) continue;
7095
7434
  void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
7096
7435
  logger.warn(`MCP server "${name2}" failed to start at boot`, err);
7097
7436
  });
@@ -7106,7 +7445,7 @@ async function createPreContextServices(input) {
7106
7445
  // surface via the SessionRegistry.
7107
7446
  isSessionInUse: async (sessionId) => {
7108
7447
  try {
7109
- const registry = getSessionRegistry(wpaths.globalRoot);
7448
+ const registry = getSessionRegistry2(wpaths.globalRoot);
7110
7449
  const live = await registry.listByProject(wpaths.projectSlug);
7111
7450
  const hit = live.find((e) => e.sessionId === sessionId);
7112
7451
  if (hit) {
@@ -7131,120 +7470,22 @@ async function createPreContextServices(input) {
7131
7470
  await input.touchProject(projectRoot, workingDir);
7132
7471
  } catch {
7133
7472
  }
7134
- let statusTracker;
7135
- try {
7136
- const registry = getSessionRegistry(wpaths.globalRoot);
7137
- await registry.register({
7138
- sessionId: session.id,
7139
- projectSlug: wpaths.projectSlug,
7473
+ const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
7474
+ config,
7475
+ events,
7476
+ logger,
7477
+ paths: {
7478
+ globalRoot: wpaths.globalRoot,
7140
7479
  projectRoot,
7141
- projectName: path14.basename(projectRoot),
7142
- workingDir,
7143
- clientType: "webui",
7144
- pid: process.pid,
7145
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
7146
- });
7147
- const fleetNotifier = new FleetNotifier({ baseDir: wpaths.globalRoot, projectRoot, selfPid: process.pid });
7148
- statusTracker = new AgentStatusTracker({
7149
- events,
7150
- registry,
7151
- sessionId: () => session.id,
7152
- onUpdate: () => fleetNotifier.notify()
7153
- });
7154
- statusTracker.start();
7155
- let stopHqSessionBridge;
7156
- let hqTelemetryPublisher;
7157
- const stopHqAuxBridges = [];
7158
- try {
7159
- const {
7160
- createHqPublisherFromEnv,
7161
- startSessionTelemetryBridge,
7162
- startFleetTelemetryBridge,
7163
- startBrainTelemetryBridge,
7164
- startWorktreeTelemetryBridge,
7165
- startToolTelemetryBridge,
7166
- startCostTelemetryBridge
7167
- } = await import("@wrongstack/core");
7168
- const hqTelemetry = createHqPublisherFromEnv({
7169
- clientKind: "webui",
7170
- projectRoot,
7171
- projectName: path14.basename(projectRoot),
7172
- appConfig: config,
7173
- socketFactory: (url) => new WebSocket2(url)
7174
- });
7175
- if (hqTelemetry) {
7176
- hqTelemetry.connect();
7177
- hqTelemetryPublisher = hqTelemetry;
7178
- stopHqSessionBridge = startSessionTelemetryBridge({
7179
- publisher: hqTelemetry,
7180
- events,
7181
- sessionId: session.id,
7182
- projectRoot,
7183
- projectName: path14.basename(projectRoot),
7184
- globalRoot: wpaths.globalRoot,
7185
- initialAgents: statusTracker?.getAgents(),
7186
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
7187
- });
7188
- try {
7189
- stopHqAuxBridges.push(
7190
- startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
7191
- );
7192
- } catch {
7193
- }
7194
- try {
7195
- stopHqAuxBridges.push(
7196
- startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
7197
- );
7198
- } catch {
7199
- }
7200
- try {
7201
- stopHqAuxBridges.push(
7202
- startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
7203
- );
7204
- } catch {
7205
- }
7206
- try {
7207
- stopHqAuxBridges.push(
7208
- startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
7209
- );
7210
- } catch {
7211
- }
7212
- try {
7213
- stopHqAuxBridges.push(
7214
- startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
7215
- );
7216
- } catch {
7217
- }
7218
- }
7219
- } catch {
7220
- }
7221
- const stopTracking = async () => {
7222
- try {
7223
- fleetNotifier.dispose();
7224
- await registry.markClosing();
7225
- statusTracker?.stop();
7226
- stopHqSessionBridge?.();
7227
- for (const stop of stopHqAuxBridges) {
7228
- try {
7229
- stop();
7230
- } catch {
7231
- }
7232
- }
7233
- hqTelemetryPublisher?.close();
7234
- } catch {
7235
- }
7236
- };
7237
- process.once("beforeExit", () => {
7238
- void stopTracking();
7239
- });
7240
- process.once("SIGINT", () => {
7241
- void stopTracking();
7242
- });
7243
- process.once("SIGTERM", () => {
7244
- void stopTracking();
7245
- });
7246
- } catch {
7247
- }
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;
7248
7489
  let context;
7249
7490
  const tokenCounter = new DefaultTokenCounter({
7250
7491
  registry: modelsRegistry,
@@ -7269,7 +7510,7 @@ async function createPreContextServices(input) {
7269
7510
  const modelCapabilitiesRef = { current: modelCapabilities };
7270
7511
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7271
7512
  const skillInstaller = config.features.skills ? new SkillInstaller({
7272
- manifestPath: path14.join(wpaths.globalRoot, "installed-skills.json"),
7513
+ manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
7273
7514
  projectSkillsDir: wpaths.inProjectSkills,
7274
7515
  globalSkillsDir: wpaths.globalSkills,
7275
7516
  projectHash: wpaths.projectHash,
@@ -7279,7 +7520,7 @@ async function createPreContextServices(input) {
7279
7520
  const bundledPromptsDir = promptsEnabled ? (() => {
7280
7521
  try {
7281
7522
  const req = createRequire2(import.meta.url);
7282
- return path14.join(path14.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7523
+ return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7283
7524
  } catch {
7284
7525
  return void 0;
7285
7526
  }
@@ -7331,6 +7572,14 @@ async function createPreContextServices(input) {
7331
7572
  const initialContextPolicy = resolveContextWindowPolicy(config.context);
7332
7573
  context.meta["contextWindowMode"] = initialContextPolicy.id;
7333
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
+ );
7334
7583
  seedContextMeta(config, context);
7335
7584
  return {
7336
7585
  modelsRegistry,
@@ -7347,6 +7596,7 @@ async function createPreContextServices(input) {
7347
7596
  session,
7348
7597
  sessionStartedAt,
7349
7598
  statusTracker,
7599
+ sessionIdentity,
7350
7600
  tokenCounter,
7351
7601
  modeStore,
7352
7602
  modeId,
@@ -7396,6 +7646,7 @@ function patchConfig(config, updates) {
7396
7646
  }
7397
7647
 
7398
7648
  // src/server/backend-services.ts
7649
+ import { join as join12 } from "node:path";
7399
7650
  import {
7400
7651
  Agent,
7401
7652
  AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
@@ -7409,9 +7660,10 @@ import {
7409
7660
  installDesignStudioMiddleware,
7410
7661
  ObservableBrainArbiter as ObservableBrainArbiterCtor,
7411
7662
  resolveContextWindowPolicy as resolveContextWindowPolicy2,
7412
- createAutonomyBrain,
7413
- createTieredBrainArbiter,
7414
- DefaultBrainArbiter,
7663
+ BrainDecisionLedger,
7664
+ createBrainRuntime,
7665
+ resolveBrainConfigDefaults,
7666
+ EscalationRoutingBrainArbiter,
7415
7667
  GlobalMailbox as GlobalMailbox2,
7416
7668
  mailboxSessionTag,
7417
7669
  SessionMemoryConsolidator,
@@ -7425,12 +7677,13 @@ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7425
7677
  var REPLAY_LIMIT = 50;
7426
7678
  var PAUSE_TIMEOUT_MS = 6e4;
7427
7679
  var CollaborationWebSocketHandler = class {
7428
- constructor(events, logger, reader, annotations, bus) {
7680
+ constructor(events, logger, reader, annotations, bus, options = {}) {
7429
7681
  this.events = events;
7430
7682
  this.logger = logger;
7431
7683
  this.reader = reader;
7432
7684
  this.annotations = annotations;
7433
7685
  this.bus = bus;
7686
+ this.options = options;
7434
7687
  this.subscribe();
7435
7688
  this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
7436
7689
  }
@@ -7439,6 +7692,7 @@ var CollaborationWebSocketHandler = class {
7439
7692
  reader;
7440
7693
  annotations;
7441
7694
  bus;
7695
+ options;
7442
7696
  clients = /* @__PURE__ */ new Set();
7443
7697
  /** sessionId → participants currently watching it. */
7444
7698
  bySession = /* @__PURE__ */ new Map();
@@ -7466,11 +7720,16 @@ var CollaborationWebSocketHandler = class {
7466
7720
  handleMessage(ws, msg) {
7467
7721
  if (msg.type === "collab.join") {
7468
7722
  const payload = msg.payload;
7469
- if (!payload?.sessionId) {
7723
+ if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
7470
7724
  this.send(ws, this.errorMessage("collab.join requires sessionId"));
7471
7725
  return true;
7472
7726
  }
7473
- this.join(ws, payload.sessionId, payload.role ?? "observer");
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);
7474
7733
  return true;
7475
7734
  }
7476
7735
  if (msg.type === "collab.leave") {
@@ -7505,6 +7764,20 @@ var CollaborationWebSocketHandler = class {
7505
7764
  }
7506
7765
  // ── Join / leave flow ──────────────────────────────────────────────────
7507
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
+ }
7508
7781
  if (role === "controller" && !this.bus) {
7509
7782
  this.send(
7510
7783
  ws,
@@ -7523,6 +7796,13 @@ var CollaborationWebSocketHandler = class {
7523
7796
  );
7524
7797
  return;
7525
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
+ }
7526
7806
  const participant = {
7527
7807
  participantId: randomUUID(),
7528
7808
  ws,
@@ -7776,17 +8056,13 @@ var CollaborationWebSocketHandler = class {
7776
8056
  type: "collab.event",
7777
8057
  payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
7778
8058
  };
7779
- const data = JSON.stringify(msg);
7780
- for (const bucket of this.bySession.values()) {
7781
- for (const p of bucket) {
7782
- try {
7783
- if (p.ws.readyState === 1) p.ws.send(data);
7784
- } catch (err) {
7785
- this.logger.debug?.(
7786
- `collab broadcast failed: ${toErrorMessage6(err)}`
7787
- );
7788
- }
7789
- }
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);
7790
8066
  }
7791
8067
  }
7792
8068
  /**
@@ -8110,7 +8386,8 @@ var CollaborationWebSocketHandler = class {
8110
8386
  * Bus callback: a queued injection was spliced into a real tool call. Re-emit
8111
8387
  * `collab.injection.granted` with phase `'consumed'` and the now-known tool
8112
8388
  * name. The injection carries no sessionId, so resolve it from the author's
8113
- * current session; if they've already left, fall back to every live session.
8389
+ * current participant. If the author has left, fail closed rather than leak
8390
+ * the payload to unrelated sessions.
8114
8391
  */
8115
8392
  broadcastInjectionConsumed(info) {
8116
8393
  let sessionId = null;
@@ -8139,14 +8416,16 @@ var CollaborationWebSocketHandler = class {
8139
8416
  if (sessionId) {
8140
8417
  this.broadcast(sessionId, message(sessionId));
8141
8418
  } else {
8142
- for (const sid of this.bySession.keys()) this.broadcast(sid, message(sid));
8419
+ this.logger.debug?.(
8420
+ `collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
8421
+ );
8143
8422
  }
8144
8423
  }
8145
8424
  };
8146
8425
 
8147
8426
  // src/server/codebase-indexing.ts
8148
8427
  import * as fs12 from "node:fs";
8149
- import * as path15 from "node:path";
8428
+ import * as path16 from "node:path";
8150
8429
  import {
8151
8430
  cancelPendingReindexes,
8152
8431
  enqueueReindex,
@@ -8199,7 +8478,7 @@ function setupWebUICodebaseIndexing(deps2) {
8199
8478
  if (!filename) return;
8200
8479
  const rel = filename.toString();
8201
8480
  if (isIgnored(rel)) return;
8202
- const abs = path15.resolve(deps2.projectRoot, rel);
8481
+ const abs = path16.resolve(deps2.projectRoot, rel);
8203
8482
  enqueueFile(abs);
8204
8483
  });
8205
8484
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
@@ -8212,7 +8491,7 @@ function setupWebUICodebaseIndexing(deps2) {
8212
8491
  }
8213
8492
  function enqueueFile(filePath) {
8214
8493
  if (!idx.onEdit && !idx.watchExternal) return;
8215
- const abs = path15.isAbsolute(filePath) ? path15.normalize(filePath) : path15.resolve(deps2.projectRoot, filePath);
8494
+ const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
8216
8495
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
8217
8496
  enqueueReindex({
8218
8497
  projectRoot: deps2.projectRoot,
@@ -8249,9 +8528,9 @@ function isIgnored(rel) {
8249
8528
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
8250
8529
  }
8251
8530
  function isInside2(root, target) {
8252
- const normalizedRoot = path15.resolve(root);
8253
- const normalizedTarget = path15.resolve(target);
8254
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path15.sep);
8531
+ const normalizedRoot = path16.resolve(root);
8532
+ const normalizedTarget = path16.resolve(target);
8533
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
8255
8534
  }
8256
8535
 
8257
8536
  // src/server/discover-mailbox-bridge.ts
@@ -8784,21 +9063,59 @@ async function createAgentServices(input) {
8784
9063
  agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
8785
9064
  }
8786
9065
  console.log("[WebUI] Agent initialized");
8787
- const brainSettings = { maxAutoRisk: "medium" };
8788
- const autonomousBrain = {
8789
- decide: (request) => createAutonomyBrain({
8790
- provider,
8791
- model: context.model,
8792
- maxAutoRisk: "all"
8793
- // the tiered ceiling gates risk — keep inner permissive
8794
- }).decide(request)
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();
9076
+ };
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
+ }
8795
9116
  };
8796
9117
  const brain = new ObservableBrainArbiterCtor(
8797
- createTieredBrainArbiter({
8798
- policy: new DefaultBrainArbiter(),
8799
- autonomous: autonomousBrain,
8800
- getMaxAutoRisk: () => brainSettings.maxAutoRisk
8801
- }),
9118
+ new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
8802
9119
  events
8803
9120
  );
8804
9121
  container.bind(TOKENS2.BrainArbiter, () => brain);
@@ -8838,6 +9155,12 @@ async function createAgentServices(input) {
8838
9155
  const brainMonitor = new BrainMonitor({
8839
9156
  events,
8840
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,
8841
9164
  sessionId: () => context.session?.id,
8842
9165
  intervene: async ({ subject, body }) => {
8843
9166
  const tag = mailboxSessionTag(input.sessionGetter().id);
@@ -8902,7 +9225,10 @@ async function createAgentServices(input) {
8902
9225
  logger,
8903
9226
  input.sessionReader,
8904
9227
  input.annotationsStore,
8905
- collabBus
9228
+ collabBus,
9229
+ {
9230
+ getActiveSessionId: () => context.session.id
9231
+ }
8906
9232
  );
8907
9233
  return {
8908
9234
  collabBus,
@@ -8914,8 +9240,13 @@ async function createAgentServices(input) {
8914
9240
  pipelines,
8915
9241
  brain,
8916
9242
  brainSettings,
9243
+ brainRuntime,
8917
9244
  brainLog,
8918
9245
  brainMonitor,
9246
+ // Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
9247
+ get brainLedger() {
9248
+ return brainLedger;
9249
+ },
8919
9250
  codebaseIndexing,
8920
9251
  autoPhaseHandler,
8921
9252
  specsHandler,
@@ -9091,7 +9422,18 @@ function createConnectionHandler(opts) {
9091
9422
  }
9092
9423
 
9093
9424
  // src/server/message-dispatcher.ts
9094
- import path16 from "node:path";
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";
9095
9437
 
9096
9438
  // src/server/autophase-routes.ts
9097
9439
  async function handleAutoPhaseRoute(_ws, msg, handlers) {
@@ -9112,6 +9454,12 @@ async function handleBrainRoute(ws, msg, handlers) {
9112
9454
  case "brain.ask":
9113
9455
  await handlers.ask(ws, msg);
9114
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;
9115
9463
  default:
9116
9464
  return false;
9117
9465
  }
@@ -9842,6 +10190,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
9842
10190
  "Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
9843
10191
  );
9844
10192
  return true;
10193
+ case "kanban.capabilities":
10194
+ ok(ws, type, { dispatchSupported: false });
10195
+ return true;
9845
10196
  case "kanban.task.remove": {
9846
10197
  const boardId = payload?.boardId;
9847
10198
  const taskId = payload?.taskId;
@@ -9921,6 +10272,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
9921
10272
  case "mailbox.purge":
9922
10273
  await handlers.purge(ws, msg);
9923
10274
  return true;
10275
+ case "mailbox.compact":
10276
+ await handlers.compact(ws, msg);
10277
+ return true;
9924
10278
  default:
9925
10279
  return false;
9926
10280
  }
@@ -9959,6 +10313,18 @@ async function handleMcpRoute(ws, msg, handlers) {
9959
10313
  case "mcp.discover":
9960
10314
  await handlers.discover(ws, msg);
9961
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;
9962
10328
  default:
9963
10329
  return false;
9964
10330
  }
@@ -10348,7 +10714,7 @@ function createMessageDispatcher(opts) {
10348
10714
  skillLoader: deps2.skillLoader,
10349
10715
  skillInstaller: deps2.skillInstaller,
10350
10716
  projectRoot,
10351
- projectSkillsDir: path16.join(projectRoot, ".wrongstack", "skills"),
10717
+ projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
10352
10718
  globalSkillsDir: deps2.wpaths.globalSkills
10353
10719
  };
10354
10720
  }
@@ -10421,7 +10787,8 @@ function createMessageDispatcher(opts) {
10421
10787
  }
10422
10788
  case "user_message": {
10423
10789
  if (!ensureCurrentSession(ws, msg, "user_message")) return;
10424
- const content = msg.payload.content;
10790
+ const userPayload = msg.payload;
10791
+ const content = userPayload.content;
10425
10792
  if (runLock.get()) {
10426
10793
  send(ws, {
10427
10794
  type: "error",
@@ -10435,8 +10802,24 @@ function createMessageDispatcher(opts) {
10435
10802
  const thisRun = new AbortController();
10436
10803
  runLock.set(thisRun);
10437
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
+ }
10438
10821
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
10439
- const result = await deps2.agent.run(content, {
10822
+ const result = await deps2.agent.run(input, {
10440
10823
  signal: thisRun.signal,
10441
10824
  maxIterations: maxIt
10442
10825
  });
@@ -10454,13 +10837,24 @@ function createMessageDispatcher(opts) {
10454
10837
  })
10455
10838
  });
10456
10839
  } catch (err) {
10457
- send(ws, {
10458
- type: "error",
10459
- payload: sessionPayload2({
10460
- phase: "agent.run",
10461
- message: errMessage(err)
10462
- })
10463
- });
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
+ }
10464
10858
  } finally {
10465
10859
  if (runLock.get() === thisRun) {
10466
10860
  runLock.set(null);
@@ -10508,7 +10902,7 @@ function createMessageDispatcher(opts) {
10508
10902
  break;
10509
10903
  }
10510
10904
  case "tool.disable": {
10511
- const name2 = (msg.payload ?? {}).name;
10905
+ const name2 = msg.payload?.name;
10512
10906
  if (!name2) {
10513
10907
  send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
10514
10908
  break;
@@ -10523,7 +10917,7 @@ function createMessageDispatcher(opts) {
10523
10917
  break;
10524
10918
  }
10525
10919
  case "tool.enable": {
10526
- const name2 = (msg.payload ?? {}).name;
10920
+ const name2 = msg.payload?.name;
10527
10921
  if (!name2) {
10528
10922
  send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
10529
10923
  break;
@@ -10567,6 +10961,14 @@ function createMessageDispatcher(opts) {
10567
10961
  throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
10568
10962
  case "mcp.discover":
10569
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");
10570
10972
  // Skills — full request→response cycle lives in skills-handlers.ts.
10571
10973
  case "skills.list":
10572
10974
  await handleSkillsList(ws, makeSkillsContext());
@@ -10867,7 +11269,19 @@ var PREF_KEYS = [
10867
11269
  "favoriteModels",
10868
11270
  "favoriteModelsOnly",
10869
11271
  "modelMatrix",
10870
- "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"
10871
11285
  ];
10872
11286
  function prefSnapshot(contextMeta) {
10873
11287
  const snapshot = {};
@@ -10938,6 +11352,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
10938
11352
  setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
10939
11353
  if (typeof payload["enhanceLanguage"] === "string")
10940
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"]);
10941
11365
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
10942
11366
  if (typeof payload["nextPrediction"] === "boolean")
10943
11367
  decrypted.nextPrediction = payload["nextPrediction"];
@@ -11055,16 +11479,34 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11055
11479
  }
11056
11480
  decrypted.modelRuntime = mr;
11057
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"];
11058
11500
  }, "prefs");
11059
11501
  }
11060
11502
 
11061
11503
  // src/server/projects-manifest.ts
11062
11504
  import * as fs14 from "node:fs/promises";
11063
- import * as path17 from "node:path";
11505
+ import * as path18 from "node:path";
11064
11506
  import { projectSlug } from "@wrongstack/core";
11065
11507
  function projectsJsonPath(globalConfigPath) {
11066
- const base = path17.dirname(globalConfigPath);
11067
- return path17.join(base, "projects.json");
11508
+ const base = path18.dirname(globalConfigPath);
11509
+ return path18.join(base, "projects.json");
11068
11510
  }
11069
11511
  async function loadManifest(globalConfigPath) {
11070
11512
  try {
@@ -11077,15 +11519,15 @@ async function loadManifest(globalConfigPath) {
11077
11519
  }
11078
11520
  async function saveManifest(manifest, globalConfigPath) {
11079
11521
  const file = projectsJsonPath(globalConfigPath);
11080
- await fs14.mkdir(path17.dirname(file), { recursive: true });
11522
+ await fs14.mkdir(path18.dirname(file), { recursive: true });
11081
11523
  await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
11082
11524
  }
11083
11525
  function generateProjectSlug(rootPath) {
11084
11526
  return projectSlug(rootPath);
11085
11527
  }
11086
11528
  async function ensureProjectDataDir(slug, globalConfigPath) {
11087
- const base = path17.dirname(globalConfigPath);
11088
- const dir = path17.join(base, "projects", slug);
11529
+ const base = path18.dirname(globalConfigPath);
11530
+ const dir = path18.join(base, "projects", slug);
11089
11531
  await fs14.mkdir(dir, { recursive: true });
11090
11532
  return dir;
11091
11533
  }
@@ -11502,6 +11944,16 @@ async function handleMailboxPurge(ws, deps2, opts) {
11502
11944
  send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
11503
11945
  }
11504
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
+ }
11505
11957
 
11506
11958
  // src/server/mode-handlers.ts
11507
11959
  import {
@@ -11590,7 +12042,7 @@ function createModeHandlers(ctx) {
11590
12042
  }
11591
12043
 
11592
12044
  // src/server/project-handlers.ts
11593
- import * as path18 from "node:path";
12045
+ import * as path19 from "node:path";
11594
12046
  function createProjectHandlers(ctx) {
11595
12047
  return {
11596
12048
  listProjects: async (ws) => {
@@ -11616,7 +12068,7 @@ function createProjectHandlers(ctx) {
11616
12068
  selectProject: async (ws, msg) => {
11617
12069
  const payload = msg.payload;
11618
12070
  const root = typeof payload?.root === "string" ? payload.root : "";
11619
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path18.basename(root) : "";
12071
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
11620
12072
  send(ws, {
11621
12073
  type: "projects.selected",
11622
12074
  payload: {
@@ -11651,12 +12103,12 @@ function createProjectHandlers(ctx) {
11651
12103
  }
11652
12104
 
11653
12105
  // src/server/session-handlers.ts
11654
- import * as path19 from "node:path";
11655
12106
  import {
11656
12107
  DEFAULT_CONTEXT_WINDOW_MODE_ID,
11657
12108
  repairToolUseAdjacency,
11658
12109
  resolveContextWindowPolicy as resolveContextWindowPolicy3
11659
12110
  } from "@wrongstack/core";
12111
+ import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
11660
12112
  function createSessionHandlers(ctx) {
11661
12113
  const currentSessionId = () => ctx.getSession().id;
11662
12114
  const sessionPayload2 = (payload) => {
@@ -11682,33 +12134,46 @@ function createSessionHandlers(ctx) {
11682
12134
  });
11683
12135
  return false;
11684
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
+ };
11685
12167
  return {
11686
12168
  newSession: async (ws, msg) => {
11687
12169
  if (!ensureCurrentSession(ws, msg, "session.new")) return;
11688
- const session = ctx.getSession();
11689
- try {
11690
- await session.append({
11691
- type: "session_end",
11692
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11693
- usage: ctx.tokenCounter.total()
11694
- });
11695
- await session.close();
11696
- } catch {
11697
- }
11698
12170
  const next = await ctx.getSessionStore().create({
11699
12171
  id: "",
11700
12172
  title: "",
11701
12173
  model: ctx.config.model,
11702
12174
  provider: ctx.config.provider
11703
12175
  });
11704
- ctx.setSession(next);
11705
- ctx.context.session = next;
11706
- ctx.context.state.replaceMessages([]);
11707
- ctx.context.state.replaceTodos([]);
11708
- ctx.context.readFiles.clear();
11709
- ctx.context.fileMtimes.clear();
11710
- ctx.tokenCounter.reset();
11711
- ctx.setSessionStartedAt(Date.now());
12176
+ await activateSession(next, []);
11712
12177
  broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
11713
12178
  },
11714
12179
  clearContext: async (ws, msg) => {
@@ -11964,23 +12429,7 @@ function createSessionHandlers(ctx) {
11964
12429
  return;
11965
12430
  }
11966
12431
  const resumed = await ctx.getSessionStore().resume(id);
11967
- try {
11968
- await current.append({
11969
- type: "session_end",
11970
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11971
- usage: ctx.tokenCounter.total()
11972
- });
11973
- await current.close();
11974
- } catch {
11975
- }
11976
- ctx.setSession(resumed.writer);
11977
- ctx.context.session = resumed.writer;
11978
- ctx.context.state.replaceMessages(resumed.data.messages);
11979
- ctx.context.readFiles.clear();
11980
- ctx.context.fileMtimes.clear();
11981
- ctx.tokenCounter.reset();
11982
- ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
11983
- ctx.setSessionStartedAt(Date.now());
12432
+ await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
11984
12433
  broadcast(ctx.clients, {
11985
12434
  type: "session.start",
11986
12435
  payload: {
@@ -12004,10 +12453,7 @@ function createSessionHandlers(ctx) {
12004
12453
  try {
12005
12454
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
12006
12455
  const projectRoot = ctx.getProjectRoot();
12007
- const rewinder = new DefaultSessionRewinder(
12008
- path19.join(projectRoot, ".wrongstack", "sessions"),
12009
- projectRoot
12010
- );
12456
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12011
12457
  const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
12012
12458
  send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
12013
12459
  } catch {
@@ -12020,10 +12466,7 @@ function createSessionHandlers(ctx) {
12020
12466
  try {
12021
12467
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
12022
12468
  const projectRoot = ctx.getProjectRoot();
12023
- const rewinder = new DefaultSessionRewinder(
12024
- path19.join(projectRoot, ".wrongstack", "sessions"),
12025
- projectRoot
12026
- );
12469
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12027
12470
  await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
12028
12471
  await ctx.context.session.truncateToCheckpoint(checkpointIndex);
12029
12472
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
@@ -12239,8 +12682,10 @@ function buildRoutes(state, deps2, cb) {
12239
12682
  getProjectRoot: state.getProjectRoot,
12240
12683
  getSession: state.getSession,
12241
12684
  getSessionStore: state.getSessionStore,
12685
+ sessionsDir: deps2.wpaths.projectSessions,
12242
12686
  setSession: state.setSession,
12243
12687
  setSessionStartedAt: state.setSessionStartedAt,
12688
+ onSessionSwapped: cb.onSessionSwapped,
12244
12689
  sessionStartPayload: cb.sessionStartPayload
12245
12690
  });
12246
12691
  const projectRoutes = createProjectHandlers({
@@ -12335,6 +12780,17 @@ function buildRoutes(state, deps2, cb) {
12335
12780
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
12336
12781
  }
12337
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
+ }
12338
12794
  if (typeof payload["logLevel"] === "string") {
12339
12795
  const valid = ["debug", "info", "warn", "error"];
12340
12796
  if (valid.includes(payload["logLevel"])) {
@@ -12412,6 +12868,13 @@ function buildRoutes(state, deps2, cb) {
12412
12868
  { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
12413
12869
  parsed.value
12414
12870
  );
12871
+ },
12872
+ compact: (ws, msg) => {
12873
+ return handleMailboxCompact(
12874
+ ws,
12875
+ { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
12876
+ msg.payload ?? {}
12877
+ );
12415
12878
  }
12416
12879
  };
12417
12880
  const mcpRoutes = {
@@ -12424,14 +12887,29 @@ function buildRoutes(state, deps2, cb) {
12424
12887
  sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12425
12888
  wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12426
12889
  restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12427
- 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
+ });
12428
12909
  };
12429
12910
  const brainRoutes = {
12430
12911
  status: (ws) => {
12431
- send(ws, {
12432
- type: "brain.status",
12433
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
12434
- });
12912
+ sendBrainStatus(ws);
12435
12913
  },
12436
12914
  risk: (ws, msg) => {
12437
12915
  const parsed = validateBrainRiskPayload(msg.payload);
@@ -12441,11 +12919,43 @@ function buildRoutes(state, deps2, cb) {
12441
12919
  }
12442
12920
  const { level } = parsed.value;
12443
12921
  deps2.brainSettings.maxAutoRisk = level;
12922
+ sendBrainStatus(ws);
12923
+ },
12924
+ configGet: (ws) => {
12444
12925
  send(ws, {
12445
- type: "brain.status",
12446
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
12926
+ type: "brain.config",
12927
+ payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
12447
12928
  });
12448
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
+ },
12449
12959
  ask: async (ws, msg) => {
12450
12960
  const parsed = validateBrainAskPayload(msg.payload);
12451
12961
  if (!parsed.ok) {
@@ -12553,7 +13063,8 @@ async function startWebUI(opts = {}) {
12553
13063
  promptsCtx,
12554
13064
  modelCapabilitiesRef,
12555
13065
  provider,
12556
- context
13066
+ context,
13067
+ sessionIdentity
12557
13068
  } = preContext;
12558
13069
  let sessionStore = preContext.sessionStore;
12559
13070
  let session = preContext.session;
@@ -12586,7 +13097,12 @@ async function startWebUI(opts = {}) {
12586
13097
  modelCapabilitiesRef,
12587
13098
  sessionGetter: () => session,
12588
13099
  sessionReader,
12589
- 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")
12590
13106
  });
12591
13107
  const {
12592
13108
  compactor,
@@ -12596,6 +13112,7 @@ async function startWebUI(opts = {}) {
12596
13112
  pipelines,
12597
13113
  brain,
12598
13114
  brainSettings,
13115
+ brainRuntime,
12599
13116
  brainLog,
12600
13117
  brainMonitor,
12601
13118
  codebaseIndexing,
@@ -12766,10 +13283,12 @@ async function startWebUI(opts = {}) {
12766
13283
  terminalHandler,
12767
13284
  brain,
12768
13285
  brainSettings,
13286
+ brainRuntime,
12769
13287
  brainLog
12770
13288
  };
12771
13289
  const cb = {
12772
13290
  sessionStartPayload,
13291
+ onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
12773
13292
  updateAutoCompactionMaxContext,
12774
13293
  updateGlobalConfig: updateGlobalConfig2,
12775
13294
  persistPrefsToConfig: persistPrefsToConfig2,
@@ -12895,7 +13414,10 @@ async function startWebUI(opts = {}) {
12895
13414
  onShutdown: async () => {
12896
13415
  credentialWatcherClose?.();
12897
13416
  brainMonitor.stop();
13417
+ await agentServices.brainLedger?.stop().catch(() => {
13418
+ });
12898
13419
  await mcpRegistry.stopAll().catch(() => void 0);
13420
+ await sessionIdentity.stop();
12899
13421
  eventArming.getDispose()?.();
12900
13422
  if (eternalSubscription) {
12901
13423
  eternalSubscription.dispose();