@wrongstack/webui-server 0.298.0 → 0.298.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -73,8 +73,7 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
73
73
  // Display-only toggles (purely visual, persisted in localStorage via Zustand).
74
74
  "groupToolCalls",
75
75
  "showThinkingLogs",
76
- // v11 Display parity: agent-swarm panel + inverse fsAccess flag.
77
- "showAgentSwarmPanel",
76
+ // v11 Display parity: inverse fsAccess flag.
78
77
  "allowOutsideProjectRoot",
79
78
  // v13 Display parity (TUI SettingsPicker fields 42 & 43): the read tool
80
79
  // includes codebase-index symbols, and SAGE memory-inject blocks are
@@ -169,7 +168,8 @@ var ENUM_PREF_KEYS = {
169
168
  // Chimera autoFix + auto-review cascade threshold
170
169
  chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
171
170
  autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
172
- fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
171
+ fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"]),
172
+ showAgentSwarmPanel: /* @__PURE__ */ new Set(["bottom", "sidebar", "off"])
173
173
  };
174
174
  function validateModelRuntimeValue(modelRuntime, path35) {
175
175
  const reasoning = modelRuntime["reasoning"];
@@ -8978,7 +8978,7 @@ function buildCspHeader(publicWsUrl, host, port) {
8978
8978
  if (host && isLoopbackHostname(host)) {
8979
8979
  const p = port ?? 3456;
8980
8980
  if (p > 0 && p <= 65535) {
8981
- for (const h of ["127.0.0.1", "localhost", "[::1]"]) {
8981
+ for (const h of ["127.0.0.1", "localhost"]) {
8982
8982
  connect.add(`ws://${h}:${p}`);
8983
8983
  connect.add(`wss://${h}:${p}`);
8984
8984
  }
@@ -15665,7 +15665,8 @@ function createProviderOperations(deps2) {
15665
15665
  providerId,
15666
15666
  config
15667
15667
  );
15668
- const siblingId = SIBLING_CATALOG[providerId];
15668
+ const siblingCatalogKey = config?.family ?? providerId;
15669
+ const siblingId = SIBLING_CATALOG[siblingCatalogKey];
15669
15670
  const sibling = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
15670
15671
  let models = resolveProviderModelList(
15671
15672
  config?.models,
@@ -15820,6 +15821,50 @@ function createProviderOperations(deps2) {
15820
15821
  sendOperationResult(ws, false, errMessage(err));
15821
15822
  }
15822
15823
  }
15824
+ async function handleCustomModelSet(ws, providerId, modelId, definition) {
15825
+ try {
15826
+ const providers = await loadConfigProviders();
15827
+ const cfg = Object.hasOwn(providers, providerId) ? providers[providerId] : void 0;
15828
+ if (!cfg) {
15829
+ sendOperationResult(ws, false, `Unknown provider "${providerId}"`);
15830
+ return;
15831
+ }
15832
+ if (!cfg.customModels) cfg.customModels = {};
15833
+ cfg.customModels[modelId] = definition;
15834
+ if (!cfg.models) cfg.models = [];
15835
+ if (!cfg.models.includes(modelId)) cfg.models.push(modelId);
15836
+ await saveConfigProviders(providers);
15837
+ sendOperationResult(ws, true, `Saved model "${modelId}" for ${providerId}`);
15838
+ broadcastSaved(providers);
15839
+ } catch (err) {
15840
+ sendOperationResult(ws, false, errMessage(err));
15841
+ }
15842
+ }
15843
+ async function handleCustomModelRemove(ws, providerId, modelId) {
15844
+ try {
15845
+ const providers = await loadConfigProviders();
15846
+ const cfg = Object.hasOwn(providers, providerId) ? providers[providerId] : void 0;
15847
+ if (!cfg) {
15848
+ sendOperationResult(ws, false, `Unknown provider "${providerId}"`);
15849
+ return;
15850
+ }
15851
+ if (cfg.customModels && Object.hasOwn(cfg.customModels, modelId)) {
15852
+ delete cfg.customModels[modelId];
15853
+ if (Object.keys(cfg.customModels).length === 0) delete cfg.customModels;
15854
+ if (cfg.models) {
15855
+ cfg.models = cfg.models.filter((m) => m !== modelId);
15856
+ if (cfg.models.length === 0) delete cfg.models;
15857
+ }
15858
+ await saveConfigProviders(providers);
15859
+ sendOperationResult(ws, true, `Removed model "${modelId}" from ${providerId}`);
15860
+ broadcastSaved(providers);
15861
+ } else {
15862
+ sendOperationResult(ws, false, `Model "${modelId}" not found in ${providerId}`);
15863
+ }
15864
+ } catch (err) {
15865
+ sendOperationResult(ws, false, errMessage(err));
15866
+ }
15867
+ }
15823
15868
  async function handleProviderUndoClear(ws, providerId, previousModels) {
15824
15869
  try {
15825
15870
  const providers = await loadConfigProviders();
@@ -15895,7 +15940,7 @@ function createProviderOperations(deps2) {
15895
15940
  const p = existing ? { ...existing } : { type: providerId };
15896
15941
  p.family = outcome.family;
15897
15942
  if (!p.baseUrl) p.baseUrl = outcome.baseUrl;
15898
- p.models = [...outcome.models];
15943
+ if (outcome.models.length > 0) p.models = [...outcome.models];
15899
15944
  const keys = normalizeKeys(p).filter((k) => k.label !== outcome.apiKey.label);
15900
15945
  keys.push(outcome.apiKey);
15901
15946
  writeKeysBack(p, keys);
@@ -16003,6 +16048,8 @@ function createProviderOperations(deps2) {
16003
16048
  handleProviderAdd,
16004
16049
  handleProviderRemove,
16005
16050
  handleProviderClearModels,
16051
+ handleCustomModelSet,
16052
+ handleCustomModelRemove,
16006
16053
  handleProviderUndoClear,
16007
16054
  handleProviderUpdate,
16008
16055
  handleProviderProbe,
@@ -16339,6 +16386,8 @@ var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
16339
16386
  "prefs.update",
16340
16387
  "provider.add",
16341
16388
  "provider.clear_models",
16389
+ "provider.custom_models.remove",
16390
+ "provider.custom_models.set",
16342
16391
  "provider.models",
16343
16392
  "provider.models.search",
16344
16393
  "provider.probe",
@@ -16365,6 +16414,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
16365
16414
  "agent.status_changed",
16366
16415
  "agent.timeline.message",
16367
16416
  "client.status_update",
16417
+ "chimera.report_available",
16368
16418
  "compaction.failed",
16369
16419
  "completion.result",
16370
16420
  "context.compacted",
@@ -18201,10 +18251,14 @@ async function handleProjectRoute(ws, msg, handlers) {
18201
18251
  }
18202
18252
 
18203
18253
  // src/server/provider-routes.ts
18254
+ import { modelsDevModelSchema } from "@wrongstack/core/models";
18204
18255
  function asPayloadRecord(msg) {
18205
18256
  const payload = msg.payload;
18206
18257
  return typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : null;
18207
18258
  }
18259
+ function isRecord4(value) {
18260
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18261
+ }
18208
18262
  function requiredString(payload, key) {
18209
18263
  const value = payload[key];
18210
18264
  return typeof value === "string" && value.trim().length > 0 ? value : null;
@@ -18227,7 +18281,7 @@ var CUSTOM_MODEL_BOOLEAN_CAPS = /* @__PURE__ */ new Set([
18227
18281
  "streaming",
18228
18282
  "jsonMode"
18229
18283
  ]);
18230
- var INLINE_DEFINITION_KEYS = /* @__PURE__ */ new Set(["name", "maxOutput", "capabilities", "provider"]);
18284
+ var INLINE_DEFINITION_KEYS = /* @__PURE__ */ new Set(["name", "maxOutput", "capabilities", "provider", "modelsDev"]);
18231
18285
  function optionalCustomModels(payload) {
18232
18286
  const value = payload["customModels"];
18233
18287
  if (value === void 0) return void 0;
@@ -18266,11 +18320,21 @@ function optionalCustomModels(payload) {
18266
18320
  }
18267
18321
  capabilities = built;
18268
18322
  }
18323
+ const rawMd = definition["modelsDev"];
18324
+ let validatedModelsDev;
18325
+ if (rawMd !== void 0) {
18326
+ if (!isRecord4(rawMd)) return null;
18327
+ const parsed = modelsDevModelSchema.safeParse({ ...rawMd, id: modelId });
18328
+ if (!parsed.success) return null;
18329
+ const { id: _parsedId, ...validMd } = parsed.data;
18330
+ validatedModelsDev = validMd;
18331
+ }
18269
18332
  out[modelId] = {
18270
18333
  ...typeof definition["name"] === "string" ? { name: definition["name"] } : {},
18271
18334
  ...typeof definition["provider"] === "string" ? { provider: definition["provider"] } : {},
18272
18335
  ...typeof definition["maxOutput"] === "number" ? { maxOutput: definition["maxOutput"] } : {},
18273
- ...capabilities ? { capabilities } : {}
18336
+ ...capabilities ? { capabilities } : {},
18337
+ ...validatedModelsDev ? { modelsDev: validatedModelsDev } : {}
18274
18338
  };
18275
18339
  }
18276
18340
  return out;
@@ -18374,6 +18438,30 @@ async function handleProviderRoute(ws, msg, routes) {
18374
18438
  await routes.providerHandlers.handleProviderClearModels(ws, providerId);
18375
18439
  return true;
18376
18440
  }
18441
+ case "provider.custom_models.set": {
18442
+ const payload = asPayloadRecord(msg);
18443
+ const providerId = payload ? requiredString(payload, "providerId") : null;
18444
+ const modelId = payload ? requiredString(payload, "modelId") : null;
18445
+ if (!payload || !providerId || !SAFE_CONFIG_KEY.test(providerId) || !modelId || !SAFE_CONFIG_KEY.test(modelId)) {
18446
+ return invalidPayload(ws, msg.type);
18447
+ }
18448
+ const customModelRaw = payload["customModel"];
18449
+ if (!isRecord4(customModelRaw)) return invalidPayload(ws, msg.type);
18450
+ const cm = optionalCustomModels({ customModels: { [modelId]: customModelRaw } });
18451
+ if (!cm || !cm[modelId]) return invalidPayload(ws, msg.type);
18452
+ await routes.providerHandlers.handleCustomModelSet(ws, providerId, modelId, cm[modelId]);
18453
+ return true;
18454
+ }
18455
+ case "provider.custom_models.remove": {
18456
+ const payload = asPayloadRecord(msg);
18457
+ const providerId = payload ? requiredString(payload, "providerId") : null;
18458
+ const modelId = payload ? requiredString(payload, "modelId") : null;
18459
+ if (!payload || !providerId || !SAFE_CONFIG_KEY.test(providerId) || !modelId || !SAFE_CONFIG_KEY.test(modelId)) {
18460
+ return invalidPayload(ws, msg.type);
18461
+ }
18462
+ await routes.providerHandlers.handleCustomModelRemove(ws, providerId, modelId);
18463
+ return true;
18464
+ }
18377
18465
  case "provider.undo_clear": {
18378
18466
  const payload = asPayloadRecord(msg);
18379
18467
  const providerId = payload ? requiredString(payload, "providerId") : null;
@@ -20053,12 +20141,67 @@ var SddWizardWebSocketHandler = class {
20053
20141
  // src/server/setup-events.ts
20054
20142
  import { recordTaskFileActivity } from "@wrongstack/kanban";
20055
20143
 
20144
+ // src/server/setup-events-core-watchers.ts
20145
+ import * as fs19 from "node:fs/promises";
20146
+ import * as path24 from "node:path";
20147
+ function registerSetupEventsCoreWatchers(deps2) {
20148
+ const { broadcast: broadcast2, clients, context } = deps2;
20149
+ const disposers = [];
20150
+ const conversationState = context.state;
20151
+ if (typeof conversationState?.onChange === "function") {
20152
+ disposers.push(
20153
+ conversationState.onChange((change) => {
20154
+ if (change.kind !== "todos_replaced") return;
20155
+ broadcast2(clients, {
20156
+ type: "todos.updated",
20157
+ payload: {
20158
+ sessionId: context.session?.id ?? "",
20159
+ todos: [...change.todos],
20160
+ revision: conversationState.revision
20161
+ }
20162
+ });
20163
+ })
20164
+ );
20165
+ }
20166
+ const projectRoot = context.projectRoot;
20167
+ if (projectRoot) {
20168
+ disposers.push(
20169
+ subscribeKanbanDaemonEvents(projectRoot, (message) => broadcast2(clients, message))
20170
+ );
20171
+ }
20172
+ return disposers;
20173
+ }
20174
+ function registerSetupEventsClientStatusWriter(deps2) {
20175
+ const { broadcast: broadcast2, clients, events, wpaths } = deps2;
20176
+ const on = (event, listener) => events.on(event, listener);
20177
+ return on("client.status", async (e) => {
20178
+ broadcast2(clients, { type: "client.status_update", payload: e });
20179
+ if (wpaths?.projectStatus) {
20180
+ try {
20181
+ const statusFile = wpaths.projectStatus(e.projectHash);
20182
+ const dir = path24.dirname(statusFile);
20183
+ await fs19.mkdir(dir, { recursive: true });
20184
+ await fs19.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
20185
+ } catch (err) {
20186
+ console.error(
20187
+ JSON.stringify({
20188
+ level: "error",
20189
+ event: "setup_events.status_write_failed",
20190
+ message: err instanceof Error ? err.message : String(err),
20191
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20192
+ })
20193
+ );
20194
+ }
20195
+ }
20196
+ });
20197
+ }
20198
+
20056
20199
  // src/server/setup-events-fleet-broadcaster.ts
20057
20200
  import { watch as fsWatch } from "node:fs";
20058
- import * as path24 from "node:path";
20201
+ import * as path25 from "node:path";
20059
20202
  function registerSetupEventsFleetBroadcaster(deps2) {
20060
20203
  const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
20061
- const globalRoot = globalConfigPath ? path24.dirname(globalConfigPath) : void 0;
20204
+ const globalRoot = globalConfigPath ? path25.dirname(globalConfigPath) : void 0;
20062
20205
  if (!globalRoot) return void 0;
20063
20206
  const disposers = [];
20064
20207
  const broadcastSessions = async () => {
@@ -20068,8 +20211,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
20068
20211
  const sessions = await registry.list();
20069
20212
  const ownEntry = sessions.find((s) => s.pid === process.pid);
20070
20213
  const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
20071
- const myRoot = path24.resolve(context.projectRoot);
20072
- const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path24.resolve(s.projectRoot) === myRoot).map((s) => ({
20214
+ const myRoot = path25.resolve(context.projectRoot);
20215
+ const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path25.resolve(s.projectRoot) === myRoot).map((s) => ({
20073
20216
  sessionId: s.sessionId,
20074
20217
  projectName: s.projectName,
20075
20218
  projectSlug: s.projectSlug,
@@ -20259,14 +20402,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
20259
20402
 
20260
20403
  // src/server/setup-events-status-watcher.ts
20261
20404
  import { watch as fsWatch2 } from "node:fs";
20262
- import * as fs19 from "node:fs/promises";
20263
- import * as path26 from "node:path";
20405
+ import * as fs20 from "node:fs/promises";
20406
+ import * as path27 from "node:path";
20264
20407
 
20265
20408
  // src/server/setup-events-watcher.ts
20266
- import * as path25 from "node:path";
20409
+ import * as path26 from "node:path";
20267
20410
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
20268
20411
  const raw = String(filename);
20269
- const relative5 = path25.isAbsolute(raw) ? path25.relative(projectsDir, raw) : raw;
20412
+ const relative5 = path26.isAbsolute(raw) ? path26.relative(projectsDir, raw) : raw;
20270
20413
  const parts = relative5.split(/[\\/]+/).filter(Boolean);
20271
20414
  if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
20272
20415
  return parts.at(-2) ?? null;
@@ -20301,7 +20444,7 @@ function logFileWatcherMetrics(metrics) {
20301
20444
  function registerSetupEventsStatusWatcher(deps2) {
20302
20445
  const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
20303
20446
  if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
20304
- const projectsDir = path26.join(wpaths.globalRoot, "projects");
20447
+ const projectsDir = path27.join(wpaths.globalRoot, "projects");
20305
20448
  const knownProjectHashes = /* @__PURE__ */ new Set();
20306
20449
  const debounceTimers = /* @__PURE__ */ new Map();
20307
20450
  const DEBOUNCE_MS2 = 150;
@@ -20346,7 +20489,7 @@ function registerSetupEventsStatusWatcher(deps2) {
20346
20489
  let watcher;
20347
20490
  const startWatcher = async () => {
20348
20491
  try {
20349
- await fs19.mkdir(projectsDir, { recursive: true });
20492
+ await fs20.mkdir(projectsDir, { recursive: true });
20350
20493
  if (isDisposed()) return;
20351
20494
  watcher = fsWatch2(
20352
20495
  projectsDir,
@@ -20360,8 +20503,8 @@ function registerSetupEventsStatusWatcher(deps2) {
20360
20503
  if (!knownProjectHashes.has(projectHash)) return;
20361
20504
  if (watcherMetrics) watcherMetrics.filesProcessed++;
20362
20505
  try {
20363
- const targetFile = path26.join(projectsDir, projectHash, "status.json");
20364
- const content = await fs19.readFile(targetFile, "utf-8");
20506
+ const targetFile = path27.join(projectsDir, projectHash, "status.json");
20507
+ const content = await fs20.readFile(targetFile, "utf-8");
20365
20508
  const statusData = JSON.parse(content);
20366
20509
  scheduleBroadcast(projectHash, statusData);
20367
20510
  } catch {
@@ -20417,61 +20560,6 @@ function registerSetupEventsStatusWatcher(deps2) {
20417
20560
  };
20418
20561
  }
20419
20562
 
20420
- // src/server/setup-events-core-watchers.ts
20421
- import * as fs20 from "node:fs/promises";
20422
- import * as path27 from "node:path";
20423
- function registerSetupEventsCoreWatchers(deps2) {
20424
- const { broadcast: broadcast2, clients, context } = deps2;
20425
- const disposers = [];
20426
- const conversationState = context.state;
20427
- if (typeof conversationState?.onChange === "function") {
20428
- disposers.push(
20429
- conversationState.onChange((change) => {
20430
- if (change.kind !== "todos_replaced") return;
20431
- broadcast2(clients, {
20432
- type: "todos.updated",
20433
- payload: {
20434
- sessionId: context.session?.id ?? "",
20435
- todos: [...change.todos],
20436
- revision: conversationState.revision
20437
- }
20438
- });
20439
- })
20440
- );
20441
- }
20442
- const projectRoot = context.projectRoot;
20443
- if (projectRoot) {
20444
- disposers.push(
20445
- subscribeKanbanDaemonEvents(projectRoot, (message) => broadcast2(clients, message))
20446
- );
20447
- }
20448
- return disposers;
20449
- }
20450
- function registerSetupEventsClientStatusWriter(deps2) {
20451
- const { broadcast: broadcast2, clients, events, wpaths } = deps2;
20452
- const on = (event, listener) => events.on(event, listener);
20453
- return on("client.status", async (e) => {
20454
- broadcast2(clients, { type: "client.status_update", payload: e });
20455
- if (wpaths?.projectStatus) {
20456
- try {
20457
- const statusFile = wpaths.projectStatus(e.projectHash);
20458
- const dir = path27.dirname(statusFile);
20459
- await fs20.mkdir(dir, { recursive: true });
20460
- await fs20.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
20461
- } catch (err) {
20462
- console.error(
20463
- JSON.stringify({
20464
- level: "error",
20465
- event: "setup_events.status_write_failed",
20466
- message: err instanceof Error ? err.message : String(err),
20467
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20468
- })
20469
- );
20470
- }
20471
- }
20472
- });
20473
- }
20474
-
20475
20563
  // src/server/setup-events.ts
20476
20564
  function setupEvents(deps2) {
20477
20565
  const {
@@ -21027,6 +21115,12 @@ function setupEvents(deps2) {
21027
21115
  });
21028
21116
  });
21029
21117
  disposers.push(
21118
+ events.onPattern("chimera.report_available", (_event, payload) => {
21119
+ broadcast2(clients, {
21120
+ type: "chimera.report_available",
21121
+ payload
21122
+ });
21123
+ }),
21030
21124
  events.onPattern("mailbox.received", (_e, payload) => {
21031
21125
  broadcast2(clients, { type: "mailbox.received", payload });
21032
21126
  }),
@@ -21543,10 +21637,12 @@ import {
21543
21637
  } from "@wrongstack/core/utils";
21544
21638
  import { makeLightSubagentFactory } from "@wrongstack/runtime";
21545
21639
  import {
21640
+ createSageContextMonitorMiddleware,
21546
21641
  createSageToolCallMiddleware,
21547
21642
  createSageTurnMiddleware,
21548
21643
  getSageRetrieval,
21549
- getSageService
21644
+ getSageService,
21645
+ InjectionTracker
21550
21646
  } from "@wrongstack/sage";
21551
21647
 
21552
21648
  // src/server/discover-mailbox-bridge.ts
@@ -22394,6 +22490,8 @@ async function createAgentServices(input) {
22394
22490
  installDesignStudioMiddleware({ pipelines, ctx: context });
22395
22491
  const memoryRetrieval = getSageRetrieval(memoryStore);
22396
22492
  if (config.features.memory !== false && config.Sage?.enabled !== false && memoryRetrieval) {
22493
+ const sageInjectionTracker = new InjectionTracker();
22494
+ const getSageSessionId = () => input.sessionGetter().id;
22397
22495
  if (config.Sage?.inject?.toolResults !== false) {
22398
22496
  pipelines.toolCall.use(
22399
22497
  createSageToolCallMiddleware({
@@ -22411,7 +22509,9 @@ async function createAgentServices(input) {
22411
22509
  // middleware falls back to ctx.session.id for cooldown but passes
22412
22510
  // undefined to retrieval, causing owned session-scoped memories to be
22413
22511
  // silently excluded from tool-call injection.
22414
- getSessionId: () => input.sessionGetter().id
22512
+ getSessionId: getSageSessionId,
22513
+ tracker: sageInjectionTracker,
22514
+ events
22415
22515
  })
22416
22516
  );
22417
22517
  }
@@ -22421,10 +22521,19 @@ async function createAgentServices(input) {
22421
22521
  memory: memoryRetrieval,
22422
22522
  maxMemories: config.Sage?.inject?.maxTurnMemories,
22423
22523
  maxChars: config.Sage?.inject?.maxCharsPerTurn,
22424
- minScore: config.Sage?.inject?.minScore
22524
+ minScore: config.Sage?.inject?.minScore,
22525
+ getSessionId: getSageSessionId,
22526
+ tracker: sageInjectionTracker
22425
22527
  })
22426
22528
  );
22427
22529
  }
22530
+ pipelines.request.use(
22531
+ createSageContextMonitorMiddleware({
22532
+ tracker: sageInjectionTracker,
22533
+ events,
22534
+ getSessionId: getSageSessionId
22535
+ })
22536
+ );
22428
22537
  }
22429
22538
  const codebaseIndexing = setupWebUICodebaseIndexing({
22430
22539
  config,
@@ -1,3 +1,3 @@
1
1
  export declare const CLIENT_WORKSPACE_MESSAGE_TYPES: readonly ['files.list', 'files.read', 'files.tree', 'files.write', 'git.changes', 'git.diff', 'git.info', 'projects.add', 'projects.list', 'projects.select', 'working_dir.set', 'worktree.cleanup', 'worktree.diff', 'worktree.merge', 'worktree.remove', 'worktree.scan', 'shell.open', 'process.kill', 'process.killAll', 'process.list', 'terminal.close', 'terminal.create', 'terminal.input', 'terminal.resize'];
2
- export declare const CLIENT_CONFIGURATION_MESSAGE_TYPES: readonly ['codebase.index.server.shutdown', 'connections.health', 'connections.service_action', 'diag.get', 'key.add', 'key.delete', 'key.set_active', 'key.update', 'prefs.get', 'prefs.update', 'provider.add', 'provider.clear_models', 'provider.models', 'provider.models.search', 'provider.probe', 'provider.remove', 'provider.status.clear', 'provider.status.get', 'provider.status.retry', 'provider.undo_clear', 'provider.update', 'providers.list', 'providers.saved', 'tool.disable', 'tool.enable', 'tools.list', 'webui.shutdown'];
2
+ export declare const CLIENT_CONFIGURATION_MESSAGE_TYPES: readonly ['codebase.index.server.shutdown', 'connections.health', 'connections.service_action', 'diag.get', 'key.add', 'key.delete', 'key.set_active', 'key.update', 'prefs.get', 'prefs.update', 'provider.add', 'provider.clear_models', 'provider.custom_models.remove', 'provider.custom_models.set', 'provider.models', 'provider.models.search', 'provider.probe', 'provider.remove', 'provider.status.clear', 'provider.status.get', 'provider.status.retry', 'provider.undo_clear', 'provider.update', 'providers.list', 'providers.saved', 'tool.disable', 'tool.enable', 'tools.list', 'webui.shutdown'];
3
3
  //# sourceMappingURL=client-workspace.d.ts.map
@@ -289,6 +289,8 @@ var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
289
289
  "prefs.update",
290
290
  "provider.add",
291
291
  "provider.clear_models",
292
+ "provider.custom_models.remove",
293
+ "provider.custom_models.set",
292
294
  "provider.models",
293
295
  "provider.models.search",
294
296
  "provider.probe",
@@ -315,6 +317,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
315
317
  "agent.status_changed",
316
318
  "agent.timeline.message",
317
319
  "client.status_update",
320
+ "chimera.report_available",
318
321
  "compaction.failed",
319
322
  "completion.result",
320
323
  "context.compacted",
@@ -1,5 +1,5 @@
1
- export declare const CLIENT_MESSAGE_TYPES: readonly ["abort", "ping", "user_message", "tool.confirm_result", "completion.request", "model.switch", "model.refine", "autonomy.switch", "context.clear", "context.compact", "context.debug", "context.editor.open", "context.editor.validate", "context.editor.apply", "context.mode.create", "context.mode.delete", "context.mode.switch", "context.mode.update", "context.modes.list", "context.repair", "mode.switch", "modes.list", "session.checkpoints", "session.delete", "session.new", "session.rename", "session.resume", "session.rewind", "session.save", "sessions.list", "side_effects.list", "stats.get", "todo.update", "todos.clear", "todos.get", "todos.remove", "collab.join", "collab.leave", "collab.annotate", "collab.resolve", "collab.request_pause", "collab.resume", "collab.grant_control", "collab.inject_tool", "mailbox.action", "mailbox.agents", "mailbox.clear", "mailbox.compact", "mailbox.messages", "mailbox.purge", "mailbox.send", "files.list", "files.read", "files.tree", "files.write", "git.changes", "git.diff", "git.info", "projects.add", "projects.list", "projects.select", "working_dir.set", "worktree.cleanup", "worktree.diff", "worktree.merge", "worktree.remove", "worktree.scan", "shell.open", "process.kill", "process.killAll", "process.list", "terminal.close", "terminal.create", "terminal.input", "terminal.resize", "codebase.index.server.shutdown", "connections.health", "connections.service_action", "diag.get", "key.add", "key.delete", "key.set_active", "key.update", "prefs.get", "prefs.update", "provider.add", "provider.clear_models", "provider.models", "provider.models.search", "provider.probe", "provider.remove", "provider.status.clear", "provider.status.get", "provider.status.retry", "provider.undo_clear", "provider.update", "providers.list", "providers.saved", "tool.disable", "tool.enable", "tools.list", "webui.shutdown", "goal-state.get", "goal.addTask", "goal.assess", "goal.assignTask", "goal.clear", "goal.get", "goal.list", "goal.load", "goal.moveTask", "goal.pause", "goal.resume", "goal.retryTask", "goal.revert", "goal.runTask", "goal.save", "goal.selectPhase", "goal.start", "goal.state", "goal.status", "goal.stop", "goal.taskStatus", "goal.toggleAutonomous", "plan.get", "plan.item.update", "plan.template_use", "task.update", "tasks.get", "sdd.board.cancel_task", "sdd.board.cleanup_worktrees", "sdd.board.delete_task", "sdd.board.destroy", "sdd.board.get", "sdd.board.list", "sdd.board.pause", "sdd.board.reassign", "sdd.board.resume", "sdd.board.retry", "sdd.board.retry_all_failed", "sdd.board.rollback", "sdd.board.set_task_fallbacks", "sdd.board.set_task_model", "sdd.board.set_task_verification", "sdd.board.split_task", "sdd.board.stop", "sdd.run.from_graph", "sdd.run.from_spec", "sdd.run.start", "sdd.spec.approve", "sdd.spec.discard", "sdd.spec.get", "sdd.spec.message", "sdd.spec.start", "specs.get", "specs.list", "specs.taskStatus", "brain.ask", "brain.config.get", "brain.config.set", "brain.risk", "brain.status", "chronicle.facet", "chronicle.facets", "chronicle.graph", "chronicle.metrics", "chronicle.query", "chronicle.status", "config.doctor", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.update", "auth.oauth.cancel", "auth.oauth.code", "auth.oauth.start", "mcp.add", "mcp.disable", "mcp.discover", "mcp.enable", "mcp.list", "mcp.prompt.get", "mcp.prompts", "mcp.remove", "mcp.resource.read", "mcp.resources", "mcp.restart", "mcp.sleep", "mcp.update", "mcp.wake", "prompts.content", "prompts.create", "prompts.favorite", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.create", "skills.edit", "skills.export", "skills.install", "skills.list", "skills.uninstall", "skills.update"];
2
- export declare const SERVER_MESSAGE_TYPES: readonly ["error", "log", "pong", "side_effects", "agent.status_changed", "agent.timeline.message", "client.status_update", "compaction.failed", "completion.result", "context.compacted", "context.debug", "context.editor.snapshot", "context.editor.validation", "context.editor.applied", "context.mode.changed", "context.modes.list", "context.repaired", "ctx.max_context", "ctx.pct", "delegate.completed", "delegate.started", "iteration.completed", "iteration.limit_reached", "iteration.started", "model.refine_result", "modes.list", "provider.active_blocked", "provider.error", "provider.fallback", "provider.response", "provider.retry", "provider.status_changed", "provider.stream_error", "provider.text_delta", "provider.thinking_delta", "run.result", "session.checkpoints", "session.damaged", "session.end", "session.rewound", "session.start", "session.stats", "sessions.list", "sessions.status_update", "stats.get", "token.cost_estimate_unavailable", "token.threshold", "tool.confirm_needed", "tool.disabled", "tool.enabled", "tool.executed", "tool.loop_detected", "tool.progress", "tool.started", "tools.list", "trust.persisted", "collab.annotation.added", "collab.annotation.resolved", "collab.event", "collab.injection.granted", "collab.participant.joined", "collab.participant.left", "collab.pause.granted", "collab.pause.released", "collab.state", "mailbox.action_result", "mailbox.agent_registered", "mailbox.agents", "mailbox.cleared", "mailbox.compacted", "mailbox.event", "mailbox.messages", "mailbox.sent", "mailbox.purged", "mailbox.received", "subagent.budget_extended", "subagent.event", "checkpoint.written", "codemap.file_event", "codemap.index_updated", "codemap.tool_executed", "codemap.tool_started", "file.saved", "files.list", "files.read", "files.tree", "files.written", "git.changes", "git.diff", "git.info", "process.list", "projects.added", "projects.list", "projects.selected", "terminal.exit", "terminal.output", "working_dir.changed", "worktree.cleanup_result", "worktree.diff_result", "worktree.event", "worktree.merge_result", "worktree.orphans", "worktree.state", "auth.oauth.status", "codebase.index.server.shutdown_result", "connections.health_error", "connections.health_result", "connections.service_action_result", "diag.get", "key.operation_result", "model.switch_result", "prefs.updated", "provider.catalog", "provider.models", "provider.models.search_result", "provider.probe", "provider.status.snapshot", "providers.saved", "budget.decision", "budget.threshold_reached", "coordinator.stats", "coordinator.status", "eternal.iteration", "fleet.concurrency_update", "goal-state.updated", "goal.assess.result", "goal.list", "goal.paused", "goal.resumed", "goal.saved", "goal.error", "goal.stopped", "goal.failed", "goal.completed", "goal.cleared", "goal.reverted", "goal.progress", "goal.state", "in_flight.ended", "in_flight.started", "plan.updated", "task.completed", "task.failed", "task.pending", "task.started", "tasks.updated", "todos.cleared", "todos.updated", "kanban.task.activity", "sdd.board.lifecycle_result", "sdd.board.list", "sdd.board.snapshot", "sdd.run.started", "sdd.spec.agent_text", "sdd.spec.error", "sdd.spec.snapshot", "specs.detail", "specs.list", "consensus.vote_cast", "consensus.vote_initiated", "consensus.vote_resolved", "cron.job_fired", "cron.snapshot", "techstack.job.cancelled", "techstack.job.failed", "techstack.job.progress", "techstack.job.started", "techstack.report.delivered", "techstack.report.ready", "techstack.snapshot.updated", "techstack.workspace.completed", "brain.answer", "brain.config", "brain.event", "brain.status", "chronicle.error", "chronicle.facet_result", "chronicle.facets_result", "chronicle.graph_result", "chronicle.metrics_result", "chronicle.query_result", "chronicle.status_result", "config.doctor.result", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.event", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.update", "mcp.content.error", "mcp.content.selected", "mcp.list", "mcp.operation_result", "mcp.prompts", "mcp.resources", "mcp.server.added", "mcp.server.connected", "mcp.server.disconnected", "mcp.server.discovered", "mcp.server.error", "mcp.server.reconnected", "mcp.server.removed", "mcp.server.sleeping", "mcp.server.updated", "mcp.server.waking", "prompts.content", "prompts.created", "prompts.favorite", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.created", "skills.edited", "skills.exported", "skills.installed", "skills.list", "skills.uninstalled", "skills.updated"];
1
+ export declare const CLIENT_MESSAGE_TYPES: readonly ["abort", "ping", "user_message", "tool.confirm_result", "completion.request", "model.switch", "model.refine", "autonomy.switch", "context.clear", "context.compact", "context.debug", "context.editor.open", "context.editor.validate", "context.editor.apply", "context.mode.create", "context.mode.delete", "context.mode.switch", "context.mode.update", "context.modes.list", "context.repair", "mode.switch", "modes.list", "session.checkpoints", "session.delete", "session.new", "session.rename", "session.resume", "session.rewind", "session.save", "sessions.list", "side_effects.list", "stats.get", "todo.update", "todos.clear", "todos.get", "todos.remove", "collab.join", "collab.leave", "collab.annotate", "collab.resolve", "collab.request_pause", "collab.resume", "collab.grant_control", "collab.inject_tool", "mailbox.action", "mailbox.agents", "mailbox.clear", "mailbox.compact", "mailbox.messages", "mailbox.purge", "mailbox.send", "files.list", "files.read", "files.tree", "files.write", "git.changes", "git.diff", "git.info", "projects.add", "projects.list", "projects.select", "working_dir.set", "worktree.cleanup", "worktree.diff", "worktree.merge", "worktree.remove", "worktree.scan", "shell.open", "process.kill", "process.killAll", "process.list", "terminal.close", "terminal.create", "terminal.input", "terminal.resize", "codebase.index.server.shutdown", "connections.health", "connections.service_action", "diag.get", "key.add", "key.delete", "key.set_active", "key.update", "prefs.get", "prefs.update", "provider.add", "provider.clear_models", "provider.custom_models.remove", "provider.custom_models.set", "provider.models", "provider.models.search", "provider.probe", "provider.remove", "provider.status.clear", "provider.status.get", "provider.status.retry", "provider.undo_clear", "provider.update", "providers.list", "providers.saved", "tool.disable", "tool.enable", "tools.list", "webui.shutdown", "goal-state.get", "goal.addTask", "goal.assess", "goal.assignTask", "goal.clear", "goal.get", "goal.list", "goal.load", "goal.moveTask", "goal.pause", "goal.resume", "goal.retryTask", "goal.revert", "goal.runTask", "goal.save", "goal.selectPhase", "goal.start", "goal.state", "goal.status", "goal.stop", "goal.taskStatus", "goal.toggleAutonomous", "plan.get", "plan.item.update", "plan.template_use", "task.update", "tasks.get", "sdd.board.cancel_task", "sdd.board.cleanup_worktrees", "sdd.board.delete_task", "sdd.board.destroy", "sdd.board.get", "sdd.board.list", "sdd.board.pause", "sdd.board.reassign", "sdd.board.resume", "sdd.board.retry", "sdd.board.retry_all_failed", "sdd.board.rollback", "sdd.board.set_task_fallbacks", "sdd.board.set_task_model", "sdd.board.set_task_verification", "sdd.board.split_task", "sdd.board.stop", "sdd.run.from_graph", "sdd.run.from_spec", "sdd.run.start", "sdd.spec.approve", "sdd.spec.discard", "sdd.spec.get", "sdd.spec.message", "sdd.spec.start", "specs.get", "specs.list", "specs.taskStatus", "brain.ask", "brain.config.get", "brain.config.set", "brain.risk", "brain.status", "chronicle.facet", "chronicle.facets", "chronicle.graph", "chronicle.metrics", "chronicle.query", "chronicle.status", "config.doctor", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.update", "auth.oauth.cancel", "auth.oauth.code", "auth.oauth.start", "mcp.add", "mcp.disable", "mcp.discover", "mcp.enable", "mcp.list", "mcp.prompt.get", "mcp.prompts", "mcp.remove", "mcp.resource.read", "mcp.resources", "mcp.restart", "mcp.sleep", "mcp.update", "mcp.wake", "prompts.content", "prompts.create", "prompts.favorite", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.create", "skills.edit", "skills.export", "skills.install", "skills.list", "skills.uninstall", "skills.update"];
2
+ export declare const SERVER_MESSAGE_TYPES: readonly ["error", "log", "pong", "side_effects", "agent.status_changed", "agent.timeline.message", "client.status_update", "chimera.report_available", "compaction.failed", "completion.result", "context.compacted", "context.debug", "context.editor.snapshot", "context.editor.validation", "context.editor.applied", "context.mode.changed", "context.modes.list", "context.repaired", "ctx.max_context", "ctx.pct", "delegate.completed", "delegate.started", "iteration.completed", "iteration.limit_reached", "iteration.started", "model.refine_result", "modes.list", "provider.active_blocked", "provider.error", "provider.fallback", "provider.response", "provider.retry", "provider.status_changed", "provider.stream_error", "provider.text_delta", "provider.thinking_delta", "run.result", "session.checkpoints", "session.damaged", "session.end", "session.rewound", "session.start", "session.stats", "sessions.list", "sessions.status_update", "stats.get", "token.cost_estimate_unavailable", "token.threshold", "tool.confirm_needed", "tool.disabled", "tool.enabled", "tool.executed", "tool.loop_detected", "tool.progress", "tool.started", "tools.list", "trust.persisted", "collab.annotation.added", "collab.annotation.resolved", "collab.event", "collab.injection.granted", "collab.participant.joined", "collab.participant.left", "collab.pause.granted", "collab.pause.released", "collab.state", "mailbox.action_result", "mailbox.agent_registered", "mailbox.agents", "mailbox.cleared", "mailbox.compacted", "mailbox.event", "mailbox.messages", "mailbox.sent", "mailbox.purged", "mailbox.received", "subagent.budget_extended", "subagent.event", "checkpoint.written", "codemap.file_event", "codemap.index_updated", "codemap.tool_executed", "codemap.tool_started", "file.saved", "files.list", "files.read", "files.tree", "files.written", "git.changes", "git.diff", "git.info", "process.list", "projects.added", "projects.list", "projects.selected", "terminal.exit", "terminal.output", "working_dir.changed", "worktree.cleanup_result", "worktree.diff_result", "worktree.event", "worktree.merge_result", "worktree.orphans", "worktree.state", "auth.oauth.status", "codebase.index.server.shutdown_result", "connections.health_error", "connections.health_result", "connections.service_action_result", "diag.get", "key.operation_result", "model.switch_result", "prefs.updated", "provider.catalog", "provider.models", "provider.models.search_result", "provider.probe", "provider.status.snapshot", "providers.saved", "budget.decision", "budget.threshold_reached", "coordinator.stats", "coordinator.status", "eternal.iteration", "fleet.concurrency_update", "goal-state.updated", "goal.assess.result", "goal.list", "goal.paused", "goal.resumed", "goal.saved", "goal.error", "goal.stopped", "goal.failed", "goal.completed", "goal.cleared", "goal.reverted", "goal.progress", "goal.state", "in_flight.ended", "in_flight.started", "plan.updated", "task.completed", "task.failed", "task.pending", "task.started", "tasks.updated", "todos.cleared", "todos.updated", "kanban.task.activity", "sdd.board.lifecycle_result", "sdd.board.list", "sdd.board.snapshot", "sdd.run.started", "sdd.spec.agent_text", "sdd.spec.error", "sdd.spec.snapshot", "specs.detail", "specs.list", "consensus.vote_cast", "consensus.vote_initiated", "consensus.vote_resolved", "cron.job_fired", "cron.snapshot", "techstack.job.cancelled", "techstack.job.failed", "techstack.job.progress", "techstack.job.started", "techstack.report.delivered", "techstack.report.ready", "techstack.snapshot.updated", "techstack.workspace.completed", "brain.answer", "brain.config", "brain.event", "brain.status", "chronicle.error", "chronicle.facet_result", "chronicle.facets_result", "chronicle.graph_result", "chronicle.metrics_result", "chronicle.query_result", "chronicle.status_result", "config.doctor.result", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.event", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.update", "mcp.content.error", "mcp.content.selected", "mcp.list", "mcp.operation_result", "mcp.prompts", "mcp.resources", "mcp.server.added", "mcp.server.connected", "mcp.server.disconnected", "mcp.server.discovered", "mcp.server.error", "mcp.server.reconnected", "mcp.server.removed", "mcp.server.sleeping", "mcp.server.updated", "mcp.server.waking", "prompts.content", "prompts.created", "prompts.favorite", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.created", "skills.edited", "skills.exported", "skills.installed", "skills.list", "skills.uninstalled", "skills.updated"];
3
3
  export type ExactClientMessageType = (typeof CLIENT_MESSAGE_TYPES)[number];
4
4
  export type ExactServerMessageType = (typeof SERVER_MESSAGE_TYPES)[number];
5
5
  export declare function isRegisteredMessageType(type: string, direction: 'client' | 'server'): boolean;
@@ -1,3 +1,3 @@
1
- export declare const SERVER_CONVERSATION_MESSAGE_TYPES: readonly ['error', 'log', 'pong', 'side_effects', 'agent.status_changed', 'agent.timeline.message', 'client.status_update', 'compaction.failed', 'completion.result', 'context.compacted', 'context.debug', 'context.editor.snapshot', 'context.editor.validation', 'context.editor.applied', 'context.mode.changed', 'context.modes.list', 'context.repaired', 'ctx.max_context', 'ctx.pct', 'delegate.completed', 'delegate.started', 'iteration.completed', 'iteration.limit_reached', 'iteration.started', 'model.refine_result', 'modes.list', 'provider.active_blocked', 'provider.error', 'provider.fallback', 'provider.response', 'provider.retry', 'provider.status_changed', 'provider.stream_error', 'provider.text_delta', 'provider.thinking_delta', 'run.result', 'session.checkpoints', 'session.damaged', 'session.end', 'session.rewound', 'session.start', 'session.stats', 'sessions.list', 'sessions.status_update', 'stats.get', 'token.cost_estimate_unavailable', 'token.threshold', 'tool.confirm_needed', 'tool.disabled', 'tool.enabled', 'tool.executed', 'tool.loop_detected', 'tool.progress', 'tool.started', 'tools.list', 'trust.persisted'];
1
+ export declare const SERVER_CONVERSATION_MESSAGE_TYPES: readonly ['error', 'log', 'pong', 'side_effects', 'agent.status_changed', 'agent.timeline.message', 'client.status_update', 'chimera.report_available', 'compaction.failed', 'completion.result', 'context.compacted', 'context.debug', 'context.editor.snapshot', 'context.editor.validation', 'context.editor.applied', 'context.mode.changed', 'context.modes.list', 'context.repaired', 'ctx.max_context', 'ctx.pct', 'delegate.completed', 'delegate.started', 'iteration.completed', 'iteration.limit_reached', 'iteration.started', 'model.refine_result', 'modes.list', 'provider.active_blocked', 'provider.error', 'provider.fallback', 'provider.response', 'provider.retry', 'provider.status_changed', 'provider.stream_error', 'provider.text_delta', 'provider.thinking_delta', 'run.result', 'session.checkpoints', 'session.damaged', 'session.end', 'session.rewound', 'session.start', 'session.stats', 'sessions.list', 'sessions.status_update', 'stats.get', 'token.cost_estimate_unavailable', 'token.threshold', 'tool.confirm_needed', 'tool.disabled', 'tool.enabled', 'tool.executed', 'tool.loop_detected', 'tool.progress', 'tool.started', 'tools.list', 'trust.persisted'];
2
2
  export declare const SERVER_COLLABORATION_MESSAGE_TYPES: readonly ['collab.annotation.added', 'collab.annotation.resolved', 'collab.event', 'collab.injection.granted', 'collab.participant.joined', 'collab.participant.left', 'collab.pause.granted', 'collab.pause.released', 'collab.state', 'mailbox.action_result', 'mailbox.agent_registered', 'mailbox.agents', 'mailbox.cleared', 'mailbox.compacted', 'mailbox.event', 'mailbox.messages', 'mailbox.sent', 'mailbox.purged', 'mailbox.received', 'subagent.budget_extended', 'subagent.event'];
3
3
  //# sourceMappingURL=server-conversation.d.ts.map
@@ -54,6 +54,8 @@ export declare function createEmbeddedProviderOperations(ctx: EmbeddedProviderCo
54
54
  }) => Promise<void>;
55
55
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
56
56
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
57
+ handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
58
+ handleCustomModelRemove: (ws: WebSocket, providerId: string, modelId: string) => Promise<void>;
57
59
  handleProviderUndoClear: (ws: WebSocket, providerId: string, previousModels: string[]) => Promise<void>;
58
60
  handleProviderUpdate: (ws: WebSocket, payload: {
59
61
  id: string;
@@ -77,8 +77,7 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
77
77
  // Display-only toggles (purely visual, persisted in localStorage via Zustand).
78
78
  "groupToolCalls",
79
79
  "showThinkingLogs",
80
- // v11 Display parity: agent-swarm panel + inverse fsAccess flag.
81
- "showAgentSwarmPanel",
80
+ // v11 Display parity: inverse fsAccess flag.
82
81
  "allowOutsideProjectRoot",
83
82
  // v13 Display parity (TUI SettingsPicker fields 42 & 43): the read tool
84
83
  // includes codebase-index symbols, and SAGE memory-inject blocks are
@@ -173,7 +172,8 @@ var ENUM_PREF_KEYS = {
173
172
  // Chimera autoFix + auto-review cascade threshold
174
173
  chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
175
174
  autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
176
- fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
175
+ fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"]),
176
+ showAgentSwarmPanel: /* @__PURE__ */ new Set(["bottom", "sidebar", "off"])
177
177
  };
178
178
  function validateModelRuntimeValue(modelRuntime, path30) {
179
179
  const reasoning = modelRuntime["reasoning"];
@@ -8381,7 +8381,7 @@ function buildCspHeader(publicWsUrl, host, port) {
8381
8381
  if (host && isLoopbackHostname(host)) {
8382
8382
  const p = port ?? 3456;
8383
8383
  if (p > 0 && p <= 65535) {
8384
- for (const h of ["127.0.0.1", "localhost", "[::1]"]) {
8384
+ for (const h of ["127.0.0.1", "localhost"]) {
8385
8385
  connect.add(`ws://${h}:${p}`);
8386
8386
  connect.add(`wss://${h}:${p}`);
8387
8387
  }
@@ -13762,7 +13762,8 @@ function createProviderOperations(deps2) {
13762
13762
  providerId,
13763
13763
  config
13764
13764
  );
13765
- const siblingId = SIBLING_CATALOG[providerId];
13765
+ const siblingCatalogKey = config?.family ?? providerId;
13766
+ const siblingId = SIBLING_CATALOG[siblingCatalogKey];
13766
13767
  const sibling = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
13767
13768
  let models = resolveProviderModelList(
13768
13769
  config?.models,
@@ -13917,6 +13918,50 @@ function createProviderOperations(deps2) {
13917
13918
  sendOperationResult(ws, false, errMessage(err));
13918
13919
  }
13919
13920
  }
13921
+ async function handleCustomModelSet(ws, providerId, modelId, definition) {
13922
+ try {
13923
+ const providers = await loadConfigProviders();
13924
+ const cfg = Object.hasOwn(providers, providerId) ? providers[providerId] : void 0;
13925
+ if (!cfg) {
13926
+ sendOperationResult(ws, false, `Unknown provider "${providerId}"`);
13927
+ return;
13928
+ }
13929
+ if (!cfg.customModels) cfg.customModels = {};
13930
+ cfg.customModels[modelId] = definition;
13931
+ if (!cfg.models) cfg.models = [];
13932
+ if (!cfg.models.includes(modelId)) cfg.models.push(modelId);
13933
+ await saveConfigProviders(providers);
13934
+ sendOperationResult(ws, true, `Saved model "${modelId}" for ${providerId}`);
13935
+ broadcastSaved(providers);
13936
+ } catch (err) {
13937
+ sendOperationResult(ws, false, errMessage(err));
13938
+ }
13939
+ }
13940
+ async function handleCustomModelRemove(ws, providerId, modelId) {
13941
+ try {
13942
+ const providers = await loadConfigProviders();
13943
+ const cfg = Object.hasOwn(providers, providerId) ? providers[providerId] : void 0;
13944
+ if (!cfg) {
13945
+ sendOperationResult(ws, false, `Unknown provider "${providerId}"`);
13946
+ return;
13947
+ }
13948
+ if (cfg.customModels && Object.hasOwn(cfg.customModels, modelId)) {
13949
+ delete cfg.customModels[modelId];
13950
+ if (Object.keys(cfg.customModels).length === 0) delete cfg.customModels;
13951
+ if (cfg.models) {
13952
+ cfg.models = cfg.models.filter((m) => m !== modelId);
13953
+ if (cfg.models.length === 0) delete cfg.models;
13954
+ }
13955
+ await saveConfigProviders(providers);
13956
+ sendOperationResult(ws, true, `Removed model "${modelId}" from ${providerId}`);
13957
+ broadcastSaved(providers);
13958
+ } else {
13959
+ sendOperationResult(ws, false, `Model "${modelId}" not found in ${providerId}`);
13960
+ }
13961
+ } catch (err) {
13962
+ sendOperationResult(ws, false, errMessage(err));
13963
+ }
13964
+ }
13920
13965
  async function handleProviderUndoClear(ws, providerId, previousModels) {
13921
13966
  try {
13922
13967
  const providers = await loadConfigProviders();
@@ -13992,7 +14037,7 @@ function createProviderOperations(deps2) {
13992
14037
  const p = existing ? { ...existing } : { type: providerId };
13993
14038
  p.family = outcome.family;
13994
14039
  if (!p.baseUrl) p.baseUrl = outcome.baseUrl;
13995
- p.models = [...outcome.models];
14040
+ if (outcome.models.length > 0) p.models = [...outcome.models];
13996
14041
  const keys = normalizeKeys(p).filter((k) => k.label !== outcome.apiKey.label);
13997
14042
  keys.push(outcome.apiKey);
13998
14043
  writeKeysBack(p, keys);
@@ -14100,6 +14145,8 @@ function createProviderOperations(deps2) {
14100
14145
  handleProviderAdd,
14101
14146
  handleProviderRemove,
14102
14147
  handleProviderClearModels,
14148
+ handleCustomModelSet,
14149
+ handleCustomModelRemove,
14103
14150
  handleProviderUndoClear,
14104
14151
  handleProviderUpdate,
14105
14152
  handleProviderProbe,
@@ -14373,6 +14420,8 @@ var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
14373
14420
  "prefs.update",
14374
14421
  "provider.add",
14375
14422
  "provider.clear_models",
14423
+ "provider.custom_models.remove",
14424
+ "provider.custom_models.set",
14376
14425
  "provider.models",
14377
14426
  "provider.models.search",
14378
14427
  "provider.probe",
@@ -14399,6 +14448,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
14399
14448
  "agent.status_changed",
14400
14449
  "agent.timeline.message",
14401
14450
  "client.status_update",
14451
+ "chimera.report_available",
14402
14452
  "compaction.failed",
14403
14453
  "completion.result",
14404
14454
  "context.compacted",
@@ -15897,10 +15947,14 @@ async function handleProjectRoute(ws, msg, handlers) {
15897
15947
  }
15898
15948
 
15899
15949
  // src/server/provider-routes.ts
15950
+ import { modelsDevModelSchema } from "@wrongstack/core/models";
15900
15951
  function asPayloadRecord(msg) {
15901
15952
  const payload = msg.payload;
15902
15953
  return typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : null;
15903
15954
  }
15955
+ function isRecord4(value) {
15956
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15957
+ }
15904
15958
  function requiredString(payload, key) {
15905
15959
  const value = payload[key];
15906
15960
  return typeof value === "string" && value.trim().length > 0 ? value : null;
@@ -15923,7 +15977,7 @@ var CUSTOM_MODEL_BOOLEAN_CAPS = /* @__PURE__ */ new Set([
15923
15977
  "streaming",
15924
15978
  "jsonMode"
15925
15979
  ]);
15926
- var INLINE_DEFINITION_KEYS = /* @__PURE__ */ new Set(["name", "maxOutput", "capabilities", "provider"]);
15980
+ var INLINE_DEFINITION_KEYS = /* @__PURE__ */ new Set(["name", "maxOutput", "capabilities", "provider", "modelsDev"]);
15927
15981
  function optionalCustomModels(payload) {
15928
15982
  const value = payload["customModels"];
15929
15983
  if (value === void 0) return void 0;
@@ -15962,11 +16016,21 @@ function optionalCustomModels(payload) {
15962
16016
  }
15963
16017
  capabilities = built;
15964
16018
  }
16019
+ const rawMd = definition["modelsDev"];
16020
+ let validatedModelsDev;
16021
+ if (rawMd !== void 0) {
16022
+ if (!isRecord4(rawMd)) return null;
16023
+ const parsed = modelsDevModelSchema.safeParse({ ...rawMd, id: modelId });
16024
+ if (!parsed.success) return null;
16025
+ const { id: _parsedId, ...validMd } = parsed.data;
16026
+ validatedModelsDev = validMd;
16027
+ }
15965
16028
  out[modelId] = {
15966
16029
  ...typeof definition["name"] === "string" ? { name: definition["name"] } : {},
15967
16030
  ...typeof definition["provider"] === "string" ? { provider: definition["provider"] } : {},
15968
16031
  ...typeof definition["maxOutput"] === "number" ? { maxOutput: definition["maxOutput"] } : {},
15969
- ...capabilities ? { capabilities } : {}
16032
+ ...capabilities ? { capabilities } : {},
16033
+ ...validatedModelsDev ? { modelsDev: validatedModelsDev } : {}
15970
16034
  };
15971
16035
  }
15972
16036
  return out;
@@ -16070,6 +16134,30 @@ async function handleProviderRoute(ws, msg, routes) {
16070
16134
  await routes.providerHandlers.handleProviderClearModels(ws, providerId);
16071
16135
  return true;
16072
16136
  }
16137
+ case "provider.custom_models.set": {
16138
+ const payload = asPayloadRecord(msg);
16139
+ const providerId = payload ? requiredString(payload, "providerId") : null;
16140
+ const modelId = payload ? requiredString(payload, "modelId") : null;
16141
+ if (!payload || !providerId || !SAFE_CONFIG_KEY.test(providerId) || !modelId || !SAFE_CONFIG_KEY.test(modelId)) {
16142
+ return invalidPayload(ws, msg.type);
16143
+ }
16144
+ const customModelRaw = payload["customModel"];
16145
+ if (!isRecord4(customModelRaw)) return invalidPayload(ws, msg.type);
16146
+ const cm = optionalCustomModels({ customModels: { [modelId]: customModelRaw } });
16147
+ if (!cm || !cm[modelId]) return invalidPayload(ws, msg.type);
16148
+ await routes.providerHandlers.handleCustomModelSet(ws, providerId, modelId, cm[modelId]);
16149
+ return true;
16150
+ }
16151
+ case "provider.custom_models.remove": {
16152
+ const payload = asPayloadRecord(msg);
16153
+ const providerId = payload ? requiredString(payload, "providerId") : null;
16154
+ const modelId = payload ? requiredString(payload, "modelId") : null;
16155
+ if (!payload || !providerId || !SAFE_CONFIG_KEY.test(providerId) || !modelId || !SAFE_CONFIG_KEY.test(modelId)) {
16156
+ return invalidPayload(ws, msg.type);
16157
+ }
16158
+ await routes.providerHandlers.handleCustomModelRemove(ws, providerId, modelId);
16159
+ return true;
16160
+ }
16073
16161
  case "provider.undo_clear": {
16074
16162
  const payload = asPayloadRecord(msg);
16075
16163
  const providerId = payload ? requiredString(payload, "providerId") : null;
@@ -17289,12 +17377,67 @@ var SddWizardWebSocketHandler = class {
17289
17377
  // src/server/setup-events.ts
17290
17378
  import { recordTaskFileActivity } from "@wrongstack/kanban";
17291
17379
 
17380
+ // src/server/setup-events-core-watchers.ts
17381
+ import * as fs17 from "node:fs/promises";
17382
+ import * as path19 from "node:path";
17383
+ function registerSetupEventsCoreWatchers(deps2) {
17384
+ const { broadcast: broadcast2, clients, context } = deps2;
17385
+ const disposers = [];
17386
+ const conversationState = context.state;
17387
+ if (typeof conversationState?.onChange === "function") {
17388
+ disposers.push(
17389
+ conversationState.onChange((change) => {
17390
+ if (change.kind !== "todos_replaced") return;
17391
+ broadcast2(clients, {
17392
+ type: "todos.updated",
17393
+ payload: {
17394
+ sessionId: context.session?.id ?? "",
17395
+ todos: [...change.todos],
17396
+ revision: conversationState.revision
17397
+ }
17398
+ });
17399
+ })
17400
+ );
17401
+ }
17402
+ const projectRoot = context.projectRoot;
17403
+ if (projectRoot) {
17404
+ disposers.push(
17405
+ subscribeKanbanDaemonEvents(projectRoot, (message) => broadcast2(clients, message))
17406
+ );
17407
+ }
17408
+ return disposers;
17409
+ }
17410
+ function registerSetupEventsClientStatusWriter(deps2) {
17411
+ const { broadcast: broadcast2, clients, events, wpaths } = deps2;
17412
+ const on = (event, listener) => events.on(event, listener);
17413
+ return on("client.status", async (e) => {
17414
+ broadcast2(clients, { type: "client.status_update", payload: e });
17415
+ if (wpaths?.projectStatus) {
17416
+ try {
17417
+ const statusFile = wpaths.projectStatus(e.projectHash);
17418
+ const dir = path19.dirname(statusFile);
17419
+ await fs17.mkdir(dir, { recursive: true });
17420
+ await fs17.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
17421
+ } catch (err) {
17422
+ console.error(
17423
+ JSON.stringify({
17424
+ level: "error",
17425
+ event: "setup_events.status_write_failed",
17426
+ message: err instanceof Error ? err.message : String(err),
17427
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17428
+ })
17429
+ );
17430
+ }
17431
+ }
17432
+ });
17433
+ }
17434
+
17292
17435
  // src/server/setup-events-fleet-broadcaster.ts
17293
17436
  import { watch as fsWatch } from "node:fs";
17294
- import * as path19 from "node:path";
17437
+ import * as path20 from "node:path";
17295
17438
  function registerSetupEventsFleetBroadcaster(deps2) {
17296
17439
  const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
17297
- const globalRoot = globalConfigPath ? path19.dirname(globalConfigPath) : void 0;
17440
+ const globalRoot = globalConfigPath ? path20.dirname(globalConfigPath) : void 0;
17298
17441
  if (!globalRoot) return void 0;
17299
17442
  const disposers = [];
17300
17443
  const broadcastSessions = async () => {
@@ -17304,8 +17447,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
17304
17447
  const sessions = await registry.list();
17305
17448
  const ownEntry = sessions.find((s) => s.pid === process.pid);
17306
17449
  const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
17307
- const myRoot = path19.resolve(context.projectRoot);
17308
- const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path19.resolve(s.projectRoot) === myRoot).map((s) => ({
17450
+ const myRoot = path20.resolve(context.projectRoot);
17451
+ const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path20.resolve(s.projectRoot) === myRoot).map((s) => ({
17309
17452
  sessionId: s.sessionId,
17310
17453
  projectName: s.projectName,
17311
17454
  projectSlug: s.projectSlug,
@@ -17495,14 +17638,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
17495
17638
 
17496
17639
  // src/server/setup-events-status-watcher.ts
17497
17640
  import { watch as fsWatch2 } from "node:fs";
17498
- import * as fs17 from "node:fs/promises";
17499
- import * as path21 from "node:path";
17641
+ import * as fs18 from "node:fs/promises";
17642
+ import * as path22 from "node:path";
17500
17643
 
17501
17644
  // src/server/setup-events-watcher.ts
17502
- import * as path20 from "node:path";
17645
+ import * as path21 from "node:path";
17503
17646
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
17504
17647
  const raw = String(filename);
17505
- const relative5 = path20.isAbsolute(raw) ? path20.relative(projectsDir, raw) : raw;
17648
+ const relative5 = path21.isAbsolute(raw) ? path21.relative(projectsDir, raw) : raw;
17506
17649
  const parts = relative5.split(/[\\/]+/).filter(Boolean);
17507
17650
  if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
17508
17651
  return parts.at(-2) ?? null;
@@ -17537,7 +17680,7 @@ function logFileWatcherMetrics(metrics) {
17537
17680
  function registerSetupEventsStatusWatcher(deps2) {
17538
17681
  const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
17539
17682
  if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
17540
- const projectsDir = path21.join(wpaths.globalRoot, "projects");
17683
+ const projectsDir = path22.join(wpaths.globalRoot, "projects");
17541
17684
  const knownProjectHashes = /* @__PURE__ */ new Set();
17542
17685
  const debounceTimers = /* @__PURE__ */ new Map();
17543
17686
  const DEBOUNCE_MS = 150;
@@ -17582,7 +17725,7 @@ function registerSetupEventsStatusWatcher(deps2) {
17582
17725
  let watcher;
17583
17726
  const startWatcher = async () => {
17584
17727
  try {
17585
- await fs17.mkdir(projectsDir, { recursive: true });
17728
+ await fs18.mkdir(projectsDir, { recursive: true });
17586
17729
  if (isDisposed()) return;
17587
17730
  watcher = fsWatch2(
17588
17731
  projectsDir,
@@ -17596,8 +17739,8 @@ function registerSetupEventsStatusWatcher(deps2) {
17596
17739
  if (!knownProjectHashes.has(projectHash)) return;
17597
17740
  if (watcherMetrics) watcherMetrics.filesProcessed++;
17598
17741
  try {
17599
- const targetFile = path21.join(projectsDir, projectHash, "status.json");
17600
- const content = await fs17.readFile(targetFile, "utf-8");
17742
+ const targetFile = path22.join(projectsDir, projectHash, "status.json");
17743
+ const content = await fs18.readFile(targetFile, "utf-8");
17601
17744
  const statusData = JSON.parse(content);
17602
17745
  scheduleBroadcast(projectHash, statusData);
17603
17746
  } catch {
@@ -17653,61 +17796,6 @@ function registerSetupEventsStatusWatcher(deps2) {
17653
17796
  };
17654
17797
  }
17655
17798
 
17656
- // src/server/setup-events-core-watchers.ts
17657
- import * as fs18 from "node:fs/promises";
17658
- import * as path22 from "node:path";
17659
- function registerSetupEventsCoreWatchers(deps2) {
17660
- const { broadcast: broadcast2, clients, context } = deps2;
17661
- const disposers = [];
17662
- const conversationState = context.state;
17663
- if (typeof conversationState?.onChange === "function") {
17664
- disposers.push(
17665
- conversationState.onChange((change) => {
17666
- if (change.kind !== "todos_replaced") return;
17667
- broadcast2(clients, {
17668
- type: "todos.updated",
17669
- payload: {
17670
- sessionId: context.session?.id ?? "",
17671
- todos: [...change.todos],
17672
- revision: conversationState.revision
17673
- }
17674
- });
17675
- })
17676
- );
17677
- }
17678
- const projectRoot = context.projectRoot;
17679
- if (projectRoot) {
17680
- disposers.push(
17681
- subscribeKanbanDaemonEvents(projectRoot, (message) => broadcast2(clients, message))
17682
- );
17683
- }
17684
- return disposers;
17685
- }
17686
- function registerSetupEventsClientStatusWriter(deps2) {
17687
- const { broadcast: broadcast2, clients, events, wpaths } = deps2;
17688
- const on = (event, listener) => events.on(event, listener);
17689
- return on("client.status", async (e) => {
17690
- broadcast2(clients, { type: "client.status_update", payload: e });
17691
- if (wpaths?.projectStatus) {
17692
- try {
17693
- const statusFile = wpaths.projectStatus(e.projectHash);
17694
- const dir = path22.dirname(statusFile);
17695
- await fs18.mkdir(dir, { recursive: true });
17696
- await fs18.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
17697
- } catch (err) {
17698
- console.error(
17699
- JSON.stringify({
17700
- level: "error",
17701
- event: "setup_events.status_write_failed",
17702
- message: err instanceof Error ? err.message : String(err),
17703
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
17704
- })
17705
- );
17706
- }
17707
- }
17708
- });
17709
- }
17710
-
17711
17799
  // src/server/setup-events.ts
17712
17800
  function setupEvents(deps2) {
17713
17801
  const {
@@ -18263,6 +18351,12 @@ function setupEvents(deps2) {
18263
18351
  });
18264
18352
  });
18265
18353
  disposers.push(
18354
+ events.onPattern("chimera.report_available", (_event, payload) => {
18355
+ broadcast2(clients, {
18356
+ type: "chimera.report_available",
18357
+ payload
18358
+ });
18359
+ }),
18266
18360
  events.onPattern("mailbox.received", (_e, payload) => {
18267
18361
  broadcast2(clients, { type: "mailbox.received", payload });
18268
18362
  }),
@@ -18779,10 +18873,12 @@ import {
18779
18873
  } from "@wrongstack/core/utils";
18780
18874
  import { makeLightSubagentFactory } from "@wrongstack/runtime";
18781
18875
  import {
18876
+ createSageContextMonitorMiddleware,
18782
18877
  createSageToolCallMiddleware,
18783
18878
  createSageTurnMiddleware,
18784
18879
  getSageRetrieval,
18785
- getSageService
18880
+ getSageService,
18881
+ InjectionTracker
18786
18882
  } from "@wrongstack/sage";
18787
18883
 
18788
18884
  // src/server/discover-mailbox-bridge.ts
@@ -19630,6 +19726,8 @@ async function createAgentServices(input) {
19630
19726
  installDesignStudioMiddleware({ pipelines, ctx: context });
19631
19727
  const memoryRetrieval = getSageRetrieval(memoryStore);
19632
19728
  if (config.features.memory !== false && config.Sage?.enabled !== false && memoryRetrieval) {
19729
+ const sageInjectionTracker = new InjectionTracker();
19730
+ const getSageSessionId = () => input.sessionGetter().id;
19633
19731
  if (config.Sage?.inject?.toolResults !== false) {
19634
19732
  pipelines.toolCall.use(
19635
19733
  createSageToolCallMiddleware({
@@ -19647,7 +19745,9 @@ async function createAgentServices(input) {
19647
19745
  // middleware falls back to ctx.session.id for cooldown but passes
19648
19746
  // undefined to retrieval, causing owned session-scoped memories to be
19649
19747
  // silently excluded from tool-call injection.
19650
- getSessionId: () => input.sessionGetter().id
19748
+ getSessionId: getSageSessionId,
19749
+ tracker: sageInjectionTracker,
19750
+ events
19651
19751
  })
19652
19752
  );
19653
19753
  }
@@ -19657,10 +19757,19 @@ async function createAgentServices(input) {
19657
19757
  memory: memoryRetrieval,
19658
19758
  maxMemories: config.Sage?.inject?.maxTurnMemories,
19659
19759
  maxChars: config.Sage?.inject?.maxCharsPerTurn,
19660
- minScore: config.Sage?.inject?.minScore
19760
+ minScore: config.Sage?.inject?.minScore,
19761
+ getSessionId: getSageSessionId,
19762
+ tracker: sageInjectionTracker
19661
19763
  })
19662
19764
  );
19663
19765
  }
19766
+ pipelines.request.use(
19767
+ createSageContextMonitorMiddleware({
19768
+ tracker: sageInjectionTracker,
19769
+ events,
19770
+ getSessionId: getSageSessionId
19771
+ })
19772
+ );
19664
19773
  }
19665
19774
  const codebaseIndexing = setupWebUICodebaseIndexing({
19666
19775
  config,
@@ -89,7 +89,9 @@ export declare function injectWsConfig(html: string, opts: {
89
89
  * When set, this origin is added to `connect-src` as a `ws://`/`wss://` entry.
90
90
  * @param host - The server bind host. When it matches a known loopback address
91
91
  * (`127.0.0.1`, `::1`, `[::1]`, or `localhost`), explicit `ws://`/`wss://`
92
- * entries for all three canonical loopback hosts are added to `connect-src`.
92
+ * entries for `127.0.0.1` and `localhost` are added to `connect-src`.
93
+ * IPv6 loopback (`[::1]`) is excluded — it produces an invalid CSP source
94
+ * and is covered by `'self'` (CSP maps ws:→http:, wss:→https:).
93
95
  * @param port - The server listen port. Defaults to `3456`. Unnecessary when
94
96
  * only publicWsUrl is used (no loopback branch).
95
97
  */
@@ -98,6 +98,8 @@ export declare function createProviderOperations(deps: ProviderOperationsDeps):
98
98
  }) => Promise<void>;
99
99
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
100
100
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
101
+ handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
102
+ handleCustomModelRemove: (ws: WebSocket, providerId: string, modelId: string) => Promise<void>;
101
103
  handleProviderUndoClear: (ws: WebSocket, providerId: string, previousModels: string[]) => Promise<void>;
102
104
  handleProviderUpdate: (ws: WebSocket, payload: {
103
105
  id: string;
@@ -138,6 +140,8 @@ export declare function createProviderHandlers(deps: ProviderHandlerDeps): {
138
140
  }) => Promise<void>;
139
141
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
140
142
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
143
+ handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
144
+ handleCustomModelRemove: (ws: WebSocket, providerId: string, modelId: string) => Promise<void>;
141
145
  handleProviderUndoClear: (ws: WebSocket, providerId: string, previousModels: string[]) => Promise<void>;
142
146
  handleProviderUpdate: (ws: WebSocket, payload: {
143
147
  id: string;
@@ -17,6 +17,8 @@ export interface ProviderMutationHandlers {
17
17
  }) => Promise<void>;
18
18
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
19
19
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
20
+ handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
21
+ handleCustomModelRemove: (ws: WebSocket, providerId: string, modelId: string) => Promise<void>;
20
22
  handleProviderUndoClear: (ws: WebSocket, providerId: string, previousModels: string[]) => Promise<void>;
21
23
  handleProviderUpdate: (ws: WebSocket, payload: {
22
24
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/webui-server",
3
- "version": "0.298.0",
3
+ "version": "0.298.2",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack WebUI HTTP/WebSocket server module — extracted from @wrongstack/webui in PR #243/244 to remove the CLI -> @wrongstack/webui/server cross-package edge (audit §3.1.1). Pure backend: HTTP routes, WebSocket handlers, MCP tool wrappers, HTML serving. The web frontend lives in @wrongstack/webui; this package is the standalone server it can run on.",
6
6
  "keywords": [
@@ -40,18 +40,18 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "ws": "^8.21.1",
43
- "@wrongstack/core": "0.298.0",
44
- "@wrongstack/runtime": "0.298.0",
45
- "@wrongstack/sdd": "0.298.0",
46
- "@wrongstack/kanban": "0.298.0",
47
- "@wrongstack/mcp": "0.298.0",
48
- "@wrongstack/techstack": "0.298.0",
49
- "@wrongstack/providers": "0.298.0",
50
- "@wrongstack/sage": "0.298.0",
51
- "@wrongstack/tools": "0.298.0"
43
+ "@wrongstack/core": "0.298.2",
44
+ "@wrongstack/sdd": "0.298.2",
45
+ "@wrongstack/runtime": "0.298.2",
46
+ "@wrongstack/kanban": "0.298.2",
47
+ "@wrongstack/sage": "0.298.2",
48
+ "@wrongstack/providers": "0.298.2",
49
+ "@wrongstack/techstack": "0.298.2",
50
+ "@wrongstack/tools": "0.298.2",
51
+ "@wrongstack/mcp": "0.298.2"
52
52
  },
53
53
  "devDependencies": {
54
- "@types/node": "^26.1.1",
54
+ "@types/node": "^26.1.2",
55
55
  "@types/ws": "^8.18.1",
56
56
  "typescript": "^7.0.2"
57
57
  },