@wrongstack/webui-server 0.298.0 → 0.298.1

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
  }
@@ -16365,6 +16365,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
16365
16365
  "agent.status_changed",
16366
16366
  "agent.timeline.message",
16367
16367
  "client.status_update",
16368
+ "chimera.report_available",
16368
16369
  "compaction.failed",
16369
16370
  "completion.result",
16370
16371
  "context.compacted",
@@ -20053,12 +20054,67 @@ var SddWizardWebSocketHandler = class {
20053
20054
  // src/server/setup-events.ts
20054
20055
  import { recordTaskFileActivity } from "@wrongstack/kanban";
20055
20056
 
20057
+ // src/server/setup-events-core-watchers.ts
20058
+ import * as fs19 from "node:fs/promises";
20059
+ import * as path24 from "node:path";
20060
+ function registerSetupEventsCoreWatchers(deps2) {
20061
+ const { broadcast: broadcast2, clients, context } = deps2;
20062
+ const disposers = [];
20063
+ const conversationState = context.state;
20064
+ if (typeof conversationState?.onChange === "function") {
20065
+ disposers.push(
20066
+ conversationState.onChange((change) => {
20067
+ if (change.kind !== "todos_replaced") return;
20068
+ broadcast2(clients, {
20069
+ type: "todos.updated",
20070
+ payload: {
20071
+ sessionId: context.session?.id ?? "",
20072
+ todos: [...change.todos],
20073
+ revision: conversationState.revision
20074
+ }
20075
+ });
20076
+ })
20077
+ );
20078
+ }
20079
+ const projectRoot = context.projectRoot;
20080
+ if (projectRoot) {
20081
+ disposers.push(
20082
+ subscribeKanbanDaemonEvents(projectRoot, (message) => broadcast2(clients, message))
20083
+ );
20084
+ }
20085
+ return disposers;
20086
+ }
20087
+ function registerSetupEventsClientStatusWriter(deps2) {
20088
+ const { broadcast: broadcast2, clients, events, wpaths } = deps2;
20089
+ const on = (event, listener) => events.on(event, listener);
20090
+ return on("client.status", async (e) => {
20091
+ broadcast2(clients, { type: "client.status_update", payload: e });
20092
+ if (wpaths?.projectStatus) {
20093
+ try {
20094
+ const statusFile = wpaths.projectStatus(e.projectHash);
20095
+ const dir = path24.dirname(statusFile);
20096
+ await fs19.mkdir(dir, { recursive: true });
20097
+ await fs19.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
20098
+ } catch (err) {
20099
+ console.error(
20100
+ JSON.stringify({
20101
+ level: "error",
20102
+ event: "setup_events.status_write_failed",
20103
+ message: err instanceof Error ? err.message : String(err),
20104
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20105
+ })
20106
+ );
20107
+ }
20108
+ }
20109
+ });
20110
+ }
20111
+
20056
20112
  // src/server/setup-events-fleet-broadcaster.ts
20057
20113
  import { watch as fsWatch } from "node:fs";
20058
- import * as path24 from "node:path";
20114
+ import * as path25 from "node:path";
20059
20115
  function registerSetupEventsFleetBroadcaster(deps2) {
20060
20116
  const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
20061
- const globalRoot = globalConfigPath ? path24.dirname(globalConfigPath) : void 0;
20117
+ const globalRoot = globalConfigPath ? path25.dirname(globalConfigPath) : void 0;
20062
20118
  if (!globalRoot) return void 0;
20063
20119
  const disposers = [];
20064
20120
  const broadcastSessions = async () => {
@@ -20068,8 +20124,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
20068
20124
  const sessions = await registry.list();
20069
20125
  const ownEntry = sessions.find((s) => s.pid === process.pid);
20070
20126
  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) => ({
20127
+ const myRoot = path25.resolve(context.projectRoot);
20128
+ 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
20129
  sessionId: s.sessionId,
20074
20130
  projectName: s.projectName,
20075
20131
  projectSlug: s.projectSlug,
@@ -20259,14 +20315,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
20259
20315
 
20260
20316
  // src/server/setup-events-status-watcher.ts
20261
20317
  import { watch as fsWatch2 } from "node:fs";
20262
- import * as fs19 from "node:fs/promises";
20263
- import * as path26 from "node:path";
20318
+ import * as fs20 from "node:fs/promises";
20319
+ import * as path27 from "node:path";
20264
20320
 
20265
20321
  // src/server/setup-events-watcher.ts
20266
- import * as path25 from "node:path";
20322
+ import * as path26 from "node:path";
20267
20323
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
20268
20324
  const raw = String(filename);
20269
- const relative5 = path25.isAbsolute(raw) ? path25.relative(projectsDir, raw) : raw;
20325
+ const relative5 = path26.isAbsolute(raw) ? path26.relative(projectsDir, raw) : raw;
20270
20326
  const parts = relative5.split(/[\\/]+/).filter(Boolean);
20271
20327
  if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
20272
20328
  return parts.at(-2) ?? null;
@@ -20301,7 +20357,7 @@ function logFileWatcherMetrics(metrics) {
20301
20357
  function registerSetupEventsStatusWatcher(deps2) {
20302
20358
  const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
20303
20359
  if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
20304
- const projectsDir = path26.join(wpaths.globalRoot, "projects");
20360
+ const projectsDir = path27.join(wpaths.globalRoot, "projects");
20305
20361
  const knownProjectHashes = /* @__PURE__ */ new Set();
20306
20362
  const debounceTimers = /* @__PURE__ */ new Map();
20307
20363
  const DEBOUNCE_MS2 = 150;
@@ -20346,7 +20402,7 @@ function registerSetupEventsStatusWatcher(deps2) {
20346
20402
  let watcher;
20347
20403
  const startWatcher = async () => {
20348
20404
  try {
20349
- await fs19.mkdir(projectsDir, { recursive: true });
20405
+ await fs20.mkdir(projectsDir, { recursive: true });
20350
20406
  if (isDisposed()) return;
20351
20407
  watcher = fsWatch2(
20352
20408
  projectsDir,
@@ -20360,8 +20416,8 @@ function registerSetupEventsStatusWatcher(deps2) {
20360
20416
  if (!knownProjectHashes.has(projectHash)) return;
20361
20417
  if (watcherMetrics) watcherMetrics.filesProcessed++;
20362
20418
  try {
20363
- const targetFile = path26.join(projectsDir, projectHash, "status.json");
20364
- const content = await fs19.readFile(targetFile, "utf-8");
20419
+ const targetFile = path27.join(projectsDir, projectHash, "status.json");
20420
+ const content = await fs20.readFile(targetFile, "utf-8");
20365
20421
  const statusData = JSON.parse(content);
20366
20422
  scheduleBroadcast(projectHash, statusData);
20367
20423
  } catch {
@@ -20417,61 +20473,6 @@ function registerSetupEventsStatusWatcher(deps2) {
20417
20473
  };
20418
20474
  }
20419
20475
 
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
20476
  // src/server/setup-events.ts
20476
20477
  function setupEvents(deps2) {
20477
20478
  const {
@@ -21027,6 +21028,12 @@ function setupEvents(deps2) {
21027
21028
  });
21028
21029
  });
21029
21030
  disposers.push(
21031
+ events.onPattern("chimera.report_available", (_event, payload) => {
21032
+ broadcast2(clients, {
21033
+ type: "chimera.report_available",
21034
+ payload
21035
+ });
21036
+ }),
21030
21037
  events.onPattern("mailbox.received", (_e, payload) => {
21031
21038
  broadcast2(clients, { type: "mailbox.received", payload });
21032
21039
  }),
@@ -21543,10 +21550,12 @@ import {
21543
21550
  } from "@wrongstack/core/utils";
21544
21551
  import { makeLightSubagentFactory } from "@wrongstack/runtime";
21545
21552
  import {
21553
+ createSageContextMonitorMiddleware,
21546
21554
  createSageToolCallMiddleware,
21547
21555
  createSageTurnMiddleware,
21548
21556
  getSageRetrieval,
21549
- getSageService
21557
+ getSageService,
21558
+ InjectionTracker
21550
21559
  } from "@wrongstack/sage";
21551
21560
 
21552
21561
  // src/server/discover-mailbox-bridge.ts
@@ -22394,6 +22403,8 @@ async function createAgentServices(input) {
22394
22403
  installDesignStudioMiddleware({ pipelines, ctx: context });
22395
22404
  const memoryRetrieval = getSageRetrieval(memoryStore);
22396
22405
  if (config.features.memory !== false && config.Sage?.enabled !== false && memoryRetrieval) {
22406
+ const sageInjectionTracker = new InjectionTracker();
22407
+ const getSageSessionId = () => input.sessionGetter().id;
22397
22408
  if (config.Sage?.inject?.toolResults !== false) {
22398
22409
  pipelines.toolCall.use(
22399
22410
  createSageToolCallMiddleware({
@@ -22411,7 +22422,9 @@ async function createAgentServices(input) {
22411
22422
  // middleware falls back to ctx.session.id for cooldown but passes
22412
22423
  // undefined to retrieval, causing owned session-scoped memories to be
22413
22424
  // silently excluded from tool-call injection.
22414
- getSessionId: () => input.sessionGetter().id
22425
+ getSessionId: getSageSessionId,
22426
+ tracker: sageInjectionTracker,
22427
+ events
22415
22428
  })
22416
22429
  );
22417
22430
  }
@@ -22421,10 +22434,19 @@ async function createAgentServices(input) {
22421
22434
  memory: memoryRetrieval,
22422
22435
  maxMemories: config.Sage?.inject?.maxTurnMemories,
22423
22436
  maxChars: config.Sage?.inject?.maxCharsPerTurn,
22424
- minScore: config.Sage?.inject?.minScore
22437
+ minScore: config.Sage?.inject?.minScore,
22438
+ getSessionId: getSageSessionId,
22439
+ tracker: sageInjectionTracker
22425
22440
  })
22426
22441
  );
22427
22442
  }
22443
+ pipelines.request.use(
22444
+ createSageContextMonitorMiddleware({
22445
+ tracker: sageInjectionTracker,
22446
+ events,
22447
+ getSessionId: getSageSessionId
22448
+ })
22449
+ );
22428
22450
  }
22429
22451
  const codebaseIndexing = setupWebUICodebaseIndexing({
22430
22452
  config,
@@ -315,6 +315,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
315
315
  "agent.status_changed",
316
316
  "agent.timeline.message",
317
317
  "client.status_update",
318
+ "chimera.report_available",
318
319
  "compaction.failed",
319
320
  "completion.result",
320
321
  "context.compacted",
@@ -1,5 +1,5 @@
1
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"];
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
@@ -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
  }
@@ -14399,6 +14399,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
14399
14399
  "agent.status_changed",
14400
14400
  "agent.timeline.message",
14401
14401
  "client.status_update",
14402
+ "chimera.report_available",
14402
14403
  "compaction.failed",
14403
14404
  "completion.result",
14404
14405
  "context.compacted",
@@ -17289,12 +17290,67 @@ var SddWizardWebSocketHandler = class {
17289
17290
  // src/server/setup-events.ts
17290
17291
  import { recordTaskFileActivity } from "@wrongstack/kanban";
17291
17292
 
17293
+ // src/server/setup-events-core-watchers.ts
17294
+ import * as fs17 from "node:fs/promises";
17295
+ import * as path19 from "node:path";
17296
+ function registerSetupEventsCoreWatchers(deps2) {
17297
+ const { broadcast: broadcast2, clients, context } = deps2;
17298
+ const disposers = [];
17299
+ const conversationState = context.state;
17300
+ if (typeof conversationState?.onChange === "function") {
17301
+ disposers.push(
17302
+ conversationState.onChange((change) => {
17303
+ if (change.kind !== "todos_replaced") return;
17304
+ broadcast2(clients, {
17305
+ type: "todos.updated",
17306
+ payload: {
17307
+ sessionId: context.session?.id ?? "",
17308
+ todos: [...change.todos],
17309
+ revision: conversationState.revision
17310
+ }
17311
+ });
17312
+ })
17313
+ );
17314
+ }
17315
+ const projectRoot = context.projectRoot;
17316
+ if (projectRoot) {
17317
+ disposers.push(
17318
+ subscribeKanbanDaemonEvents(projectRoot, (message) => broadcast2(clients, message))
17319
+ );
17320
+ }
17321
+ return disposers;
17322
+ }
17323
+ function registerSetupEventsClientStatusWriter(deps2) {
17324
+ const { broadcast: broadcast2, clients, events, wpaths } = deps2;
17325
+ const on = (event, listener) => events.on(event, listener);
17326
+ return on("client.status", async (e) => {
17327
+ broadcast2(clients, { type: "client.status_update", payload: e });
17328
+ if (wpaths?.projectStatus) {
17329
+ try {
17330
+ const statusFile = wpaths.projectStatus(e.projectHash);
17331
+ const dir = path19.dirname(statusFile);
17332
+ await fs17.mkdir(dir, { recursive: true });
17333
+ await fs17.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
17334
+ } catch (err) {
17335
+ console.error(
17336
+ JSON.stringify({
17337
+ level: "error",
17338
+ event: "setup_events.status_write_failed",
17339
+ message: err instanceof Error ? err.message : String(err),
17340
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17341
+ })
17342
+ );
17343
+ }
17344
+ }
17345
+ });
17346
+ }
17347
+
17292
17348
  // src/server/setup-events-fleet-broadcaster.ts
17293
17349
  import { watch as fsWatch } from "node:fs";
17294
- import * as path19 from "node:path";
17350
+ import * as path20 from "node:path";
17295
17351
  function registerSetupEventsFleetBroadcaster(deps2) {
17296
17352
  const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
17297
- const globalRoot = globalConfigPath ? path19.dirname(globalConfigPath) : void 0;
17353
+ const globalRoot = globalConfigPath ? path20.dirname(globalConfigPath) : void 0;
17298
17354
  if (!globalRoot) return void 0;
17299
17355
  const disposers = [];
17300
17356
  const broadcastSessions = async () => {
@@ -17304,8 +17360,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
17304
17360
  const sessions = await registry.list();
17305
17361
  const ownEntry = sessions.find((s) => s.pid === process.pid);
17306
17362
  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) => ({
17363
+ const myRoot = path20.resolve(context.projectRoot);
17364
+ 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
17365
  sessionId: s.sessionId,
17310
17366
  projectName: s.projectName,
17311
17367
  projectSlug: s.projectSlug,
@@ -17495,14 +17551,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
17495
17551
 
17496
17552
  // src/server/setup-events-status-watcher.ts
17497
17553
  import { watch as fsWatch2 } from "node:fs";
17498
- import * as fs17 from "node:fs/promises";
17499
- import * as path21 from "node:path";
17554
+ import * as fs18 from "node:fs/promises";
17555
+ import * as path22 from "node:path";
17500
17556
 
17501
17557
  // src/server/setup-events-watcher.ts
17502
- import * as path20 from "node:path";
17558
+ import * as path21 from "node:path";
17503
17559
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
17504
17560
  const raw = String(filename);
17505
- const relative5 = path20.isAbsolute(raw) ? path20.relative(projectsDir, raw) : raw;
17561
+ const relative5 = path21.isAbsolute(raw) ? path21.relative(projectsDir, raw) : raw;
17506
17562
  const parts = relative5.split(/[\\/]+/).filter(Boolean);
17507
17563
  if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
17508
17564
  return parts.at(-2) ?? null;
@@ -17537,7 +17593,7 @@ function logFileWatcherMetrics(metrics) {
17537
17593
  function registerSetupEventsStatusWatcher(deps2) {
17538
17594
  const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
17539
17595
  if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
17540
- const projectsDir = path21.join(wpaths.globalRoot, "projects");
17596
+ const projectsDir = path22.join(wpaths.globalRoot, "projects");
17541
17597
  const knownProjectHashes = /* @__PURE__ */ new Set();
17542
17598
  const debounceTimers = /* @__PURE__ */ new Map();
17543
17599
  const DEBOUNCE_MS = 150;
@@ -17582,7 +17638,7 @@ function registerSetupEventsStatusWatcher(deps2) {
17582
17638
  let watcher;
17583
17639
  const startWatcher = async () => {
17584
17640
  try {
17585
- await fs17.mkdir(projectsDir, { recursive: true });
17641
+ await fs18.mkdir(projectsDir, { recursive: true });
17586
17642
  if (isDisposed()) return;
17587
17643
  watcher = fsWatch2(
17588
17644
  projectsDir,
@@ -17596,8 +17652,8 @@ function registerSetupEventsStatusWatcher(deps2) {
17596
17652
  if (!knownProjectHashes.has(projectHash)) return;
17597
17653
  if (watcherMetrics) watcherMetrics.filesProcessed++;
17598
17654
  try {
17599
- const targetFile = path21.join(projectsDir, projectHash, "status.json");
17600
- const content = await fs17.readFile(targetFile, "utf-8");
17655
+ const targetFile = path22.join(projectsDir, projectHash, "status.json");
17656
+ const content = await fs18.readFile(targetFile, "utf-8");
17601
17657
  const statusData = JSON.parse(content);
17602
17658
  scheduleBroadcast(projectHash, statusData);
17603
17659
  } catch {
@@ -17653,61 +17709,6 @@ function registerSetupEventsStatusWatcher(deps2) {
17653
17709
  };
17654
17710
  }
17655
17711
 
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
17712
  // src/server/setup-events.ts
17712
17713
  function setupEvents(deps2) {
17713
17714
  const {
@@ -18263,6 +18264,12 @@ function setupEvents(deps2) {
18263
18264
  });
18264
18265
  });
18265
18266
  disposers.push(
18267
+ events.onPattern("chimera.report_available", (_event, payload) => {
18268
+ broadcast2(clients, {
18269
+ type: "chimera.report_available",
18270
+ payload
18271
+ });
18272
+ }),
18266
18273
  events.onPattern("mailbox.received", (_e, payload) => {
18267
18274
  broadcast2(clients, { type: "mailbox.received", payload });
18268
18275
  }),
@@ -18779,10 +18786,12 @@ import {
18779
18786
  } from "@wrongstack/core/utils";
18780
18787
  import { makeLightSubagentFactory } from "@wrongstack/runtime";
18781
18788
  import {
18789
+ createSageContextMonitorMiddleware,
18782
18790
  createSageToolCallMiddleware,
18783
18791
  createSageTurnMiddleware,
18784
18792
  getSageRetrieval,
18785
- getSageService
18793
+ getSageService,
18794
+ InjectionTracker
18786
18795
  } from "@wrongstack/sage";
18787
18796
 
18788
18797
  // src/server/discover-mailbox-bridge.ts
@@ -19630,6 +19639,8 @@ async function createAgentServices(input) {
19630
19639
  installDesignStudioMiddleware({ pipelines, ctx: context });
19631
19640
  const memoryRetrieval = getSageRetrieval(memoryStore);
19632
19641
  if (config.features.memory !== false && config.Sage?.enabled !== false && memoryRetrieval) {
19642
+ const sageInjectionTracker = new InjectionTracker();
19643
+ const getSageSessionId = () => input.sessionGetter().id;
19633
19644
  if (config.Sage?.inject?.toolResults !== false) {
19634
19645
  pipelines.toolCall.use(
19635
19646
  createSageToolCallMiddleware({
@@ -19647,7 +19658,9 @@ async function createAgentServices(input) {
19647
19658
  // middleware falls back to ctx.session.id for cooldown but passes
19648
19659
  // undefined to retrieval, causing owned session-scoped memories to be
19649
19660
  // silently excluded from tool-call injection.
19650
- getSessionId: () => input.sessionGetter().id
19661
+ getSessionId: getSageSessionId,
19662
+ tracker: sageInjectionTracker,
19663
+ events
19651
19664
  })
19652
19665
  );
19653
19666
  }
@@ -19657,10 +19670,19 @@ async function createAgentServices(input) {
19657
19670
  memory: memoryRetrieval,
19658
19671
  maxMemories: config.Sage?.inject?.maxTurnMemories,
19659
19672
  maxChars: config.Sage?.inject?.maxCharsPerTurn,
19660
- minScore: config.Sage?.inject?.minScore
19673
+ minScore: config.Sage?.inject?.minScore,
19674
+ getSessionId: getSageSessionId,
19675
+ tracker: sageInjectionTracker
19661
19676
  })
19662
19677
  );
19663
19678
  }
19679
+ pipelines.request.use(
19680
+ createSageContextMonitorMiddleware({
19681
+ tracker: sageInjectionTracker,
19682
+ events,
19683
+ getSessionId: getSageSessionId
19684
+ })
19685
+ );
19664
19686
  }
19665
19687
  const codebaseIndexing = setupWebUICodebaseIndexing({
19666
19688
  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
  */
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.1",
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/kanban": "0.298.1",
44
+ "@wrongstack/core": "0.298.1",
45
+ "@wrongstack/runtime": "0.298.1",
46
+ "@wrongstack/mcp": "0.298.1",
47
+ "@wrongstack/providers": "0.298.1",
48
+ "@wrongstack/techstack": "0.298.1",
49
+ "@wrongstack/sage": "0.298.1",
50
+ "@wrongstack/sdd": "0.298.1",
51
+ "@wrongstack/tools": "0.298.1"
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
  },