@wrongstack/webui-server 0.298.3 → 0.299.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7497,7 +7497,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
7497
7497
  }
7498
7498
  try {
7499
7499
  const { SessionRegistry, DefaultSessionStore: DefaultSessionStore3, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core/storage");
7500
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
7500
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
7501
7501
  const registry = new SessionRegistry(globalRoot);
7502
7502
  const entry = await registry.get(sessionId);
7503
7503
  if (!entry) {
@@ -7505,7 +7505,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
7505
7505
  res.end(JSON.stringify({ error: "Session not found" }));
7506
7506
  return;
7507
7507
  }
7508
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
7508
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
7509
7509
  const store = new DefaultSessionStore3({ dir: paths.projectSessions });
7510
7510
  const reader = new DefaultSessionReader2({ store });
7511
7511
  const rawEntries = [];
@@ -7586,7 +7586,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
7586
7586
  try {
7587
7587
  const { SessionRegistry } = await import("@wrongstack/core/storage");
7588
7588
  const { getSharedProjectMailbox: getSharedProjectMailbox5, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
7589
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
7589
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
7590
7590
  const registry = new SessionRegistry(globalRoot);
7591
7591
  const entry = await registry.get(sessionId);
7592
7592
  if (!entry) {
@@ -7594,7 +7594,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
7594
7594
  res.end(JSON.stringify({ error: "Session not found" }));
7595
7595
  return;
7596
7596
  }
7597
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
7597
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
7598
7598
  const mailbox = getSharedProjectMailbox5(paths.projectDir);
7599
7599
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
7600
7600
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
@@ -7614,7 +7614,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
7614
7614
  try {
7615
7615
  const { SessionRegistry } = await import("@wrongstack/core/storage");
7616
7616
  const { getSharedProjectMailbox: getSharedProjectMailbox5, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
7617
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
7617
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
7618
7618
  const registry = new SessionRegistry(globalRoot);
7619
7619
  const entry = await registry.get(sessionId);
7620
7620
  if (!entry) {
@@ -7622,7 +7622,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
7622
7622
  res.end(JSON.stringify({ error: "Session not found" }));
7623
7623
  return;
7624
7624
  }
7625
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
7625
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
7626
7626
  const mailbox = getSharedProjectMailbox5(paths.projectDir);
7627
7627
  const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
7628
7628
  const [inbound, outbound] = await Promise.all([
@@ -7674,7 +7674,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
7674
7674
  try {
7675
7675
  const { SessionRegistry } = await import("@wrongstack/core/storage");
7676
7676
  const { getSharedProjectMailbox: getSharedProjectMailbox5, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
7677
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
7677
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
7678
7678
  const registry = new SessionRegistry(globalRoot);
7679
7679
  const entry = await registry.get(sessionId);
7680
7680
  if (!entry) {
@@ -7682,7 +7682,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
7682
7682
  res.end(JSON.stringify({ error: "Session not found" }));
7683
7683
  return;
7684
7684
  }
7685
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
7685
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
7686
7686
  const mailbox = getSharedProjectMailbox5(paths.projectDir);
7687
7687
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
7688
7688
  const sent = await mailbox.sendRuntimeControl({
@@ -7723,7 +7723,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
7723
7723
  try {
7724
7724
  const { SessionRegistry } = await import("@wrongstack/core/storage");
7725
7725
  const { getSharedProjectMailbox: getSharedProjectMailbox5, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
7726
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
7726
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
7727
7727
  const registry = new SessionRegistry(globalRoot);
7728
7728
  const all = await registry.list();
7729
7729
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -7735,7 +7735,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
7735
7735
  }
7736
7736
  const mbByDir = /* @__PURE__ */ new Map();
7737
7737
  const mailboxFor = (projectRoot) => {
7738
- const dir = resolveWstackPaths6({ projectRoot, globalRoot }).projectDir;
7738
+ const dir = resolveWstackPaths7({ projectRoot, globalRoot }).projectDir;
7739
7739
  let mb = mbByDir.get(dir);
7740
7740
  if (!mb) {
7741
7741
  mb = getSharedProjectMailbox5(dir);
@@ -7769,6 +7769,246 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
7769
7769
  }
7770
7770
  }
7771
7771
 
7772
+ // src/server/requirement-intake-handlers.ts
7773
+ import { readProjectIdentity } from "@wrongstack/core/utils";
7774
+ import {
7775
+ IntakeError,
7776
+ IntakeValidationError
7777
+ } from "@wrongstack/requirement-intake";
7778
+ var SERVER_ACTOR = { id: "webui-server", type: "agent" };
7779
+ var MAX_INTAKE_BODY_BYTES = 512e3;
7780
+ function intakeContext(projectId) {
7781
+ return { ...SERVER_ACTOR, projectId };
7782
+ }
7783
+ function sendJson2(res, status, body) {
7784
+ res.writeHead(status, { "Content-Type": "application/json" });
7785
+ res.end(JSON.stringify(body));
7786
+ }
7787
+ function sendNotFound(res, intakeId) {
7788
+ sendJson2(res, 404, {
7789
+ error: {
7790
+ code: "INTAKE_NOT_FOUND",
7791
+ message: `Requirement intake record not found: ${intakeId}`
7792
+ }
7793
+ });
7794
+ }
7795
+ function sendIntakeError(res, error2) {
7796
+ if (error2 instanceof IntakeValidationError) {
7797
+ sendJson2(res, 400, {
7798
+ error: { code: error2.code, message: error2.message, issues: error2.issues }
7799
+ });
7800
+ return;
7801
+ }
7802
+ if (error2 instanceof IntakeError) {
7803
+ const status = error2.code === "INTAKE_UNAUTHORIZED" ? 403 : error2.code === "INTAKE_NOT_FOUND" ? 404 : error2.code === "INTAKE_SUGGESTION_ERROR" ? 502 : 409;
7804
+ sendJson2(res, status, { error: { code: error2.code, message: error2.message } });
7805
+ return;
7806
+ }
7807
+ sendJson2(res, 500, { error: { code: "INTERNAL_ERROR", message: "Internal server error" } });
7808
+ }
7809
+ function serviceOr503(res, service) {
7810
+ if (service) return true;
7811
+ sendJson2(res, 503, {
7812
+ error: { code: "INTAKE_UNAVAILABLE", message: "Requirement intake service not configured" }
7813
+ });
7814
+ return false;
7815
+ }
7816
+ async function readJsonBody3(res, req) {
7817
+ const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
7818
+ if (contentType !== "application/json") {
7819
+ sendJson2(res, 400, {
7820
+ error: {
7821
+ code: "INVALID_CONTENT_TYPE",
7822
+ message: `Unsupported Content-Type: ${contentType || "(absent)"}`
7823
+ }
7824
+ });
7825
+ return null;
7826
+ }
7827
+ return new Promise((resolve15) => {
7828
+ let data = "";
7829
+ let failed = false;
7830
+ const fail2 = (message) => {
7831
+ if (failed) return;
7832
+ failed = true;
7833
+ sendJson2(res, 400, { error: { code: "INVALID_BODY", message } });
7834
+ resolve15(null);
7835
+ };
7836
+ req.on("data", (chunk) => {
7837
+ if (failed) return;
7838
+ data += chunk.toString("utf8");
7839
+ if (data.length > MAX_INTAKE_BODY_BYTES) {
7840
+ req.destroy();
7841
+ fail2("Request body too large");
7842
+ }
7843
+ });
7844
+ req.on("end", () => {
7845
+ if (failed) return;
7846
+ try {
7847
+ resolve15(data.trim().length === 0 ? {} : JSON.parse(data));
7848
+ } catch {
7849
+ fail2("Request body is not valid JSON");
7850
+ }
7851
+ });
7852
+ req.on("error", () => fail2("Failed to read request body"));
7853
+ });
7854
+ }
7855
+ async function discoverIntake(res, service, intakeId) {
7856
+ const record = await service.getIntake(intakeId, intakeContext(""));
7857
+ if (!record) {
7858
+ sendNotFound(res, intakeId);
7859
+ return null;
7860
+ }
7861
+ return record;
7862
+ }
7863
+ async function handleRequirementIntakeListForServer(res, service, projectRoot) {
7864
+ if (!serviceOr503(res, service)) return;
7865
+ if (!projectRoot) {
7866
+ sendJson2(res, 503, {
7867
+ error: { code: "PROJECT_ROOT_NOT_CONFIGURED", message: "Project root not configured" }
7868
+ });
7869
+ return;
7870
+ }
7871
+ try {
7872
+ const identity = await readProjectIdentity(projectRoot);
7873
+ if (!identity) {
7874
+ sendJson2(res, 404, {
7875
+ error: {
7876
+ code: "PROJECT_IDENTITY_NOT_FOUND",
7877
+ message: "No project identity found \u2014 run `wstack init` first"
7878
+ }
7879
+ });
7880
+ return;
7881
+ }
7882
+ const projectId = identity.projectId;
7883
+ const records = await service.listIntakes(projectId, intakeContext(projectId));
7884
+ sendJson2(res, 200, { projectId, intakes: records });
7885
+ } catch (error2) {
7886
+ sendIntakeError(res, error2);
7887
+ }
7888
+ }
7889
+ async function handleRequirementIntakeCreate(res, req, service, projectId) {
7890
+ if (!serviceOr503(res, service)) return;
7891
+ const body = await readJsonBody3(res, req);
7892
+ if (body === null) return;
7893
+ try {
7894
+ const result = await service.createIntake(body, intakeContext(projectId));
7895
+ sendJson2(res, result.idempotent ? 200 : 201, result);
7896
+ } catch (error2) {
7897
+ sendIntakeError(res, error2);
7898
+ }
7899
+ }
7900
+ async function handleRequirementIntakeList(res, service, projectId) {
7901
+ if (!serviceOr503(res, service)) return;
7902
+ try {
7903
+ const records = await service.listIntakes(projectId, intakeContext(projectId));
7904
+ sendJson2(res, 200, records);
7905
+ } catch (error2) {
7906
+ sendIntakeError(res, error2);
7907
+ }
7908
+ }
7909
+ async function handleRequirementIntakeGet(res, service, intakeId) {
7910
+ if (!serviceOr503(res, service)) return;
7911
+ try {
7912
+ const record = await discoverIntake(res, service, intakeId);
7913
+ if (!record) return;
7914
+ sendJson2(res, 200, record);
7915
+ } catch (error2) {
7916
+ sendIntakeError(res, error2);
7917
+ }
7918
+ }
7919
+ async function handleRequirementIntakeUpdate(res, req, service, intakeId) {
7920
+ if (!serviceOr503(res, service)) return;
7921
+ const body = await readJsonBody3(res, req);
7922
+ if (body === null) return;
7923
+ try {
7924
+ const record = await discoverIntake(res, service, intakeId);
7925
+ if (!record) return;
7926
+ const expectedVersion = typeof body["expectedVersion"] === "number" ? body["expectedVersion"] : void 0;
7927
+ const { expectedVersion: _ignored, ...patch } = body;
7928
+ void _ignored;
7929
+ const updated = await service.updateIntake(
7930
+ intakeId,
7931
+ patch,
7932
+ intakeContext(record.projectId),
7933
+ expectedVersion
7934
+ );
7935
+ sendJson2(res, 200, updated);
7936
+ } catch (error2) {
7937
+ sendIntakeError(res, error2);
7938
+ }
7939
+ }
7940
+ async function handleRequirementIntakeAnswers(res, req, service, intakeId) {
7941
+ if (!serviceOr503(res, service)) return;
7942
+ const body = await readJsonBody3(res, req);
7943
+ if (body === null) return;
7944
+ try {
7945
+ const record = await discoverIntake(res, service, intakeId);
7946
+ if (!record) return;
7947
+ const updated = await service.addAnswer(
7948
+ intakeId,
7949
+ body,
7950
+ intakeContext(record.projectId)
7951
+ );
7952
+ sendJson2(res, 200, updated);
7953
+ } catch (error2) {
7954
+ sendIntakeError(res, error2);
7955
+ }
7956
+ }
7957
+ async function handleRequirementIntakeSuggestions(res, req, service, intakeId) {
7958
+ if (!serviceOr503(res, service)) return;
7959
+ const body = await readJsonBody3(res, req);
7960
+ if (body === null) return;
7961
+ try {
7962
+ const record = await discoverIntake(res, service, intakeId);
7963
+ if (!record) return;
7964
+ const focus = Array.isArray(body["focus"]) ? body["focus"] : void 0;
7965
+ const suggestions = await service.generateSuggestions(
7966
+ intakeId,
7967
+ intakeContext(record.projectId),
7968
+ focus
7969
+ );
7970
+ sendJson2(res, 200, { suggestions });
7971
+ } catch (error2) {
7972
+ sendIntakeError(res, error2);
7973
+ }
7974
+ }
7975
+ async function handleRequirementIntakeSubmit(res, service, intakeId) {
7976
+ if (!serviceOr503(res, service)) return;
7977
+ try {
7978
+ const record = await discoverIntake(res, service, intakeId);
7979
+ if (!record) return;
7980
+ const result = await service.submitIntake(intakeId, intakeContext(record.projectId));
7981
+ sendJson2(res, 200, result);
7982
+ } catch (error2) {
7983
+ sendIntakeError(res, error2);
7984
+ }
7985
+ }
7986
+ async function handleRequirementIntakeCancel(res, req, service, intakeId) {
7987
+ if (!serviceOr503(res, service)) return;
7988
+ const body = await readJsonBody3(res, req);
7989
+ if (body === null) return;
7990
+ try {
7991
+ const record = await discoverIntake(res, service, intakeId);
7992
+ if (!record) return;
7993
+ const reason = typeof body["reason"] === "string" ? body["reason"] : void 0;
7994
+ const updated = await service.cancelIntake(intakeId, intakeContext(record.projectId), reason);
7995
+ sendJson2(res, 200, updated);
7996
+ } catch (error2) {
7997
+ sendIntakeError(res, error2);
7998
+ }
7999
+ }
8000
+ async function handleRequirementIntakeArchive(res, service, intakeId) {
8001
+ if (!serviceOr503(res, service)) return;
8002
+ try {
8003
+ const record = await discoverIntake(res, service, intakeId);
8004
+ if (!record) return;
8005
+ const updated = await service.archiveIntake(intakeId, intakeContext(record.projectId));
8006
+ sendJson2(res, 200, updated);
8007
+ } catch (error2) {
8008
+ sendIntakeError(res, error2);
8009
+ }
8010
+ }
8011
+
7772
8012
  // src/server/memory-diagnostics.ts
7773
8013
  import * as fs8 from "node:fs/promises";
7774
8014
  import * as path11 from "node:path";
@@ -7974,7 +8214,7 @@ async function touchProjectInManifest(options, globalConfigPath) {
7974
8214
  // src/server/techstack-handlers.ts
7975
8215
  import { randomUUID as randomUUID2 } from "node:crypto";
7976
8216
  var DEEP_DIVE_TIMEOUT_MS = 6e4;
7977
- function sendJson2(res, status, data) {
8217
+ function sendJson3(res, status, data) {
7978
8218
  res.writeHead(status, { "Content-Type": "application/json" });
7979
8219
  res.end(JSON.stringify(data));
7980
8220
  }
@@ -7989,13 +8229,13 @@ function handleTechStackSnapshot(res, deps2) {
7989
8229
  try {
7990
8230
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
7991
8231
  if (!snapshot) {
7992
- sendJson2(res, 404, { snapshot: null, stale: false });
8232
+ sendJson3(res, 404, { snapshot: null, stale: false });
7993
8233
  return;
7994
8234
  }
7995
8235
  const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
7996
- sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
8236
+ sendJson3(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
7997
8237
  } catch (error2) {
7998
- sendJson2(res, 500, {
8238
+ sendJson3(res, 500, {
7999
8239
  error: "TechStack store unavailable",
8000
8240
  detail: errorMessage(error2)
8001
8241
  });
@@ -8006,7 +8246,7 @@ function errorMessage(error2) {
8006
8246
  }
8007
8247
  function requireJobDeps(res, deps2) {
8008
8248
  if (!deps2.projectRoot || !deps2.engine) {
8009
- sendJson2(res, 503, { error: "TechStack engine unavailable" });
8249
+ sendJson3(res, 503, { error: "TechStack engine unavailable" });
8010
8250
  return false;
8011
8251
  }
8012
8252
  return true;
@@ -8017,7 +8257,7 @@ function startJob(res, deps2, kind) {
8017
8257
  const controller = new AbortController();
8018
8258
  deps2.runningJobs?.set(jobId, controller);
8019
8259
  deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
8020
- sendJson2(res, 202, { jobId, kind, status: "queued" });
8260
+ sendJson3(res, 202, { jobId, kind, status: "queued" });
8021
8261
  void buildResearcher(deps2, kind).catch(() => void 0).then(
8022
8262
  (researcher) => deps2.engine.analyze(deps2.projectId, {
8023
8263
  targetRoot: deps2.projectRoot,
@@ -8063,24 +8303,24 @@ function handleTechStackCancel(res, deps2, jobId) {
8063
8303
  if (controller && !controller.signal.aborted) controller.abort();
8064
8304
  deps2.store.updateJobStatus(jobId, "cancelled");
8065
8305
  deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
8066
- sendJson2(res, 200, { jobId, status: "cancelled" });
8306
+ sendJson3(res, 200, { jobId, status: "cancelled" });
8067
8307
  }
8068
8308
  async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
8069
8309
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8070
8310
  const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
8071
8311
  if (!dependency) {
8072
- sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
8312
+ sendJson3(res, 404, { error: "Dependency not found in the current snapshot" });
8073
8313
  return;
8074
8314
  }
8075
8315
  let researcher;
8076
8316
  try {
8077
8317
  researcher = await buildResearcher(deps2, "analyze");
8078
8318
  } catch (error2) {
8079
- sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error2) });
8319
+ sendJson3(res, 503, { error: "Research unavailable", detail: errorMessage(error2) });
8080
8320
  return;
8081
8321
  }
8082
8322
  if (!researcher) {
8083
- sendJson2(res, 503, {
8323
+ sendJson3(res, 503, {
8084
8324
  error: "No model configured \u2014 connect a provider to run LLM analysis."
8085
8325
  });
8086
8326
  return;
@@ -8097,9 +8337,9 @@ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
8097
8337
  [triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
8098
8338
  { signal: controller.signal }
8099
8339
  );
8100
- sendJson2(res, 200, { dependencyId, findings });
8340
+ sendJson3(res, 200, { dependencyId, findings });
8101
8341
  } catch (error2) {
8102
- sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error2) });
8342
+ sendJson3(res, 500, { error: "Research failed", detail: errorMessage(error2) });
8103
8343
  } finally {
8104
8344
  clearTimeout(timeout);
8105
8345
  controller.abort();
@@ -8108,15 +8348,15 @@ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
8108
8348
  function handleTechStackJobStatus(res, deps2, jobId) {
8109
8349
  const job = deps2.store.getJob(jobId);
8110
8350
  if (!job) {
8111
- sendJson2(res, 404, { error: "Job not found" });
8351
+ sendJson3(res, 404, { error: "Job not found" });
8112
8352
  return;
8113
8353
  }
8114
- sendJson2(res, 200, { job });
8354
+ sendJson3(res, 200, { job });
8115
8355
  }
8116
8356
  function handleTechStackReport(res, deps2, reportId, format) {
8117
8357
  const snapshot = deps2.store.getSnapshotById(reportId);
8118
8358
  if (!snapshot) {
8119
- sendJson2(res, 404, { error: "Report not found" });
8359
+ sendJson3(res, 404, { error: "Report not found" });
8120
8360
  return;
8121
8361
  }
8122
8362
  if (deps2.engine) {
@@ -8127,30 +8367,30 @@ function handleTechStackReport(res, deps2, reportId, format) {
8127
8367
  });
8128
8368
  res.end(report);
8129
8369
  } else {
8130
- sendJson2(res, 200, snapshot);
8370
+ sendJson3(res, 200, snapshot);
8131
8371
  }
8132
8372
  }
8133
8373
  async function handleTechStackTrends(res, deps2) {
8134
8374
  try {
8135
8375
  const { TrendStore } = await import("@wrongstack/techstack");
8136
- sendJson2(res, 200, { trend: new TrendStore(deps2.store).analyze(deps2.projectId) });
8376
+ sendJson3(res, 200, { trend: new TrendStore(deps2.store).analyze(deps2.projectId) });
8137
8377
  } catch (error2) {
8138
- sendJson2(res, 500, { error: "Trend analysis failed", detail: errorMessage(error2) });
8378
+ sendJson3(res, 500, { error: "Trend analysis failed", detail: errorMessage(error2) });
8139
8379
  }
8140
8380
  }
8141
8381
  async function handleTechStackRemediationPlan(res, deps2) {
8142
8382
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8143
8383
  if (!snapshot) {
8144
- sendJson2(res, 404, { error: "No TechStack snapshot is available" });
8384
+ sendJson3(res, 404, { error: "No TechStack snapshot is available" });
8145
8385
  return;
8146
8386
  }
8147
8387
  const { applyPlan, generateUpgradePlan } = await import("@wrongstack/techstack");
8148
8388
  const plan = generateUpgradePlan(snapshot);
8149
- sendJson2(res, 200, { plan, preview: await applyPlan(plan) });
8389
+ sendJson3(res, 200, { plan, preview: await applyPlan(plan) });
8150
8390
  }
8151
8391
  async function handleTechStackRemediationApply(req, res, deps2) {
8152
8392
  if (!deps2.executePackageOperation) {
8153
- sendJson2(res, 503, { error: "Permission-governed package execution is unavailable" });
8393
+ sendJson3(res, 503, { error: "Permission-governed package execution is unavailable" });
8154
8394
  return;
8155
8395
  }
8156
8396
  let approvedItems;
@@ -8163,16 +8403,16 @@ async function handleTechStackRemediationApply(req, res, deps2) {
8163
8403
  const body = JSON.parse(raw || "{}");
8164
8404
  approvedItems = Array.isArray(body.approvedItems) ? body.approvedItems.filter((value) => typeof value === "string") : [];
8165
8405
  } catch (error2) {
8166
- sendJson2(res, 400, { error: "Invalid request body", detail: errorMessage(error2) });
8406
+ sendJson3(res, 400, { error: "Invalid request body", detail: errorMessage(error2) });
8167
8407
  return;
8168
8408
  }
8169
8409
  if (approvedItems.length === 0) {
8170
- sendJson2(res, 400, { error: "approvedItems must explicitly identify at least one plan item" });
8410
+ sendJson3(res, 400, { error: "approvedItems must explicitly identify at least one plan item" });
8171
8411
  return;
8172
8412
  }
8173
8413
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8174
8414
  if (!snapshot) {
8175
- sendJson2(res, 404, { error: "No TechStack snapshot is available" });
8415
+ sendJson3(res, 404, { error: "No TechStack snapshot is available" });
8176
8416
  return;
8177
8417
  }
8178
8418
  const approved = new Set(approvedItems);
@@ -8187,14 +8427,17 @@ async function handleTechStackRemediationApply(req, res, deps2) {
8187
8427
  return executePackageOperation(operation, workspace?.relativeRoot);
8188
8428
  }
8189
8429
  });
8190
- sendJson2(res, 200, { plan, result });
8430
+ sendJson3(res, 200, { plan, result });
8191
8431
  }
8192
8432
 
8193
8433
  // src/server/ws-auth.ts
8194
8434
  import { Buffer as Buffer2 } from "node:buffer";
8195
8435
  import { timingSafeEqual } from "node:crypto";
8436
+ import { isLoopbackHost as isLoopbackHostCore } from "@wrongstack/core/hq";
8196
8437
  function isLoopbackHostname(hostname) {
8197
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
8438
+ const normalized = hostname.toLowerCase();
8439
+ if (normalized === "localhost") return true;
8440
+ return isLoopbackHostCore(normalized);
8198
8441
  }
8199
8442
  function effectivePort(url) {
8200
8443
  if (url.port) return url.port;
@@ -8215,7 +8458,7 @@ function isTrustedLoopbackOrigin(origin, hostHeader) {
8215
8458
  }
8216
8459
  }
8217
8460
  function isLoopbackBind(wsHost) {
8218
- return wsHost === "127.0.0.1" || wsHost === "::1" || wsHost === "localhost";
8461
+ return isLoopbackHostCore(wsHost) || wsHost === "localhost";
8219
8462
  }
8220
8463
  function isWildcardBind(wsHost) {
8221
8464
  return wsHost === "0.0.0.0" || wsHost === "::" || wsHost === "[::]";
@@ -8591,6 +8834,88 @@ function createHttpServer(opts) {
8591
8834
  await handleApiAnalyticsSummary(res);
8592
8835
  return;
8593
8836
  }
8837
+ if (url.pathname === "/api/requirement-intakes" && req.method === "GET") {
8838
+ if (requireAccessToken && !accessTokenOk) {
8839
+ res.writeHead(401, { "Content-Type": "application/json" });
8840
+ res.end(JSON.stringify({ error: "Unauthorized" }));
8841
+ return;
8842
+ }
8843
+ await handleRequirementIntakeListForServer(res, opts.intakeService, opts.projectRoot);
8844
+ return;
8845
+ }
8846
+ const intakeProjectMatch = url.pathname.match(
8847
+ /^\/api\/projects\/([^/]+)\/requirement-intakes$/
8848
+ );
8849
+ if (intakeProjectMatch && req.method === "POST") {
8850
+ if (requireAccessToken && !accessTokenOk) {
8851
+ res.writeHead(401, { "Content-Type": "application/json" });
8852
+ res.end(JSON.stringify({ error: "Unauthorized" }));
8853
+ return;
8854
+ }
8855
+ await handleRequirementIntakeCreate(
8856
+ res,
8857
+ req,
8858
+ opts.intakeService,
8859
+ decodeURIComponent(intakeProjectMatch[1])
8860
+ );
8861
+ return;
8862
+ }
8863
+ if (intakeProjectMatch && req.method === "GET") {
8864
+ if (requireAccessToken && !accessTokenOk) {
8865
+ res.writeHead(401, { "Content-Type": "application/json" });
8866
+ res.end(JSON.stringify({ error: "Unauthorized" }));
8867
+ return;
8868
+ }
8869
+ await handleRequirementIntakeList(
8870
+ res,
8871
+ opts.intakeService,
8872
+ decodeURIComponent(intakeProjectMatch[1])
8873
+ );
8874
+ return;
8875
+ }
8876
+ const intakeIdMatch = url.pathname.match(/^\/api\/requirement-intakes\/([^/]+)$/);
8877
+ if (intakeIdMatch && req.method === "GET") {
8878
+ if (requireAccessToken && !accessTokenOk) {
8879
+ res.writeHead(401, { "Content-Type": "application/json" });
8880
+ res.end(JSON.stringify({ error: "Unauthorized" }));
8881
+ return;
8882
+ }
8883
+ await handleRequirementIntakeGet(res, opts.intakeService, intakeIdMatch[1]);
8884
+ return;
8885
+ }
8886
+ if (intakeIdMatch && req.method === "PATCH") {
8887
+ if (requireAccessToken && !accessTokenOk) {
8888
+ res.writeHead(401, { "Content-Type": "application/json" });
8889
+ res.end(JSON.stringify({ error: "Unauthorized" }));
8890
+ return;
8891
+ }
8892
+ await handleRequirementIntakeUpdate(res, req, opts.intakeService, intakeIdMatch[1]);
8893
+ return;
8894
+ }
8895
+ const intakeActionMatch = url.pathname.match(
8896
+ /^\/api\/requirement-intakes\/([^/]+)\/(answers|suggestions|submit|cancel|archive)$/
8897
+ );
8898
+ if (intakeActionMatch && req.method === "POST") {
8899
+ if (requireAccessToken && !accessTokenOk) {
8900
+ res.writeHead(401, { "Content-Type": "application/json" });
8901
+ res.end(JSON.stringify({ error: "Unauthorized" }));
8902
+ return;
8903
+ }
8904
+ const intakeId = intakeActionMatch[1];
8905
+ const action = intakeActionMatch[2];
8906
+ if (action === "answers") {
8907
+ await handleRequirementIntakeAnswers(res, req, opts.intakeService, intakeId);
8908
+ } else if (action === "suggestions") {
8909
+ await handleRequirementIntakeSuggestions(res, req, opts.intakeService, intakeId);
8910
+ } else if (action === "submit") {
8911
+ await handleRequirementIntakeSubmit(res, opts.intakeService, intakeId);
8912
+ } else if (action === "cancel") {
8913
+ await handleRequirementIntakeCancel(res, req, opts.intakeService, intakeId);
8914
+ } else {
8915
+ await handleRequirementIntakeArchive(res, opts.intakeService, intakeId);
8916
+ }
8917
+ return;
8918
+ }
8594
8919
  if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
8595
8920
  if (requireAccessToken && !accessTokenOk) {
8596
8921
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -8786,17 +9111,25 @@ function createHttpServer(opts) {
8786
9111
  res.end(JSON.stringify({ error: "Project root not configured" }));
8787
9112
  return;
8788
9113
  }
8789
- await handleDeadCodeScan(res, {
8790
- projectRoot: opts.projectRoot,
8791
- indexDir: opts.indexDir
8792
- }, req);
9114
+ await handleDeadCodeScan(
9115
+ res,
9116
+ {
9117
+ projectRoot: opts.projectRoot,
9118
+ indexDir: opts.indexDir
9119
+ },
9120
+ req
9121
+ );
8793
9122
  return;
8794
9123
  }
8795
9124
  if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
8796
- await handleDeadCodeActionPlan(res, {
8797
- projectRoot: opts.projectRoot ?? "",
8798
- indexDir: opts.indexDir
8799
- }, req);
9125
+ await handleDeadCodeActionPlan(
9126
+ res,
9127
+ {
9128
+ projectRoot: opts.projectRoot ?? "",
9129
+ indexDir: opts.indexDir
9130
+ },
9131
+ req
9132
+ );
8800
9133
  return;
8801
9134
  }
8802
9135
  if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
@@ -12272,6 +12605,25 @@ async function findFreePort(host, startPort, opts = {}) {
12272
12605
  });
12273
12606
  }
12274
12607
 
12608
+ // src/server/intake-service.ts
12609
+ import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
12610
+ import {
12611
+ AllowAllIntakeAuthorizer,
12612
+ RequirementIntakeService,
12613
+ RequirementIntakeStore
12614
+ } from "@wrongstack/requirement-intake";
12615
+ function createProjectIntakeService(opts) {
12616
+ return new RequirementIntakeService({
12617
+ store: new RequirementIntakeStore({
12618
+ baseDir: resolveWstackPaths4({
12619
+ projectRoot: opts.projectRoot,
12620
+ globalRoot: opts.globalRoot
12621
+ }).projectRequirementIntakes
12622
+ }),
12623
+ authorizer: new AllowAllIntakeAuthorizer()
12624
+ });
12625
+ }
12626
+
12275
12627
  // src/server/network-info.ts
12276
12628
  import * as os2 from "node:os";
12277
12629
  function isTailscaleIPv4(addr) {
@@ -12327,7 +12679,21 @@ function formatExternalAccessUrls(opts) {
12327
12679
  }
12328
12680
 
12329
12681
  // src/server/brain-handlers.ts
12682
+ import { BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
12330
12683
  import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
12684
+ var COUNCIL_PERSONA_CATALOG = Object.freeze(
12685
+ BUILTIN_COUNCIL_PERSONAS.map(
12686
+ (persona) => Object.freeze({
12687
+ id: persona.id,
12688
+ name: persona.name,
12689
+ description: persona.description,
12690
+ ...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {}
12691
+ })
12692
+ )
12693
+ );
12694
+ function brainConfigPayload(runtime) {
12695
+ return { ...runtime.getSnapshot(), personaCatalog: COUNCIL_PERSONA_CATALOG };
12696
+ }
12331
12697
  function sendResult6(ctx, ws, success, message) {
12332
12698
  ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
12333
12699
  }
@@ -12367,7 +12733,7 @@ function handleBrainConfigGet(ctx, ws) {
12367
12733
  }
12368
12734
  ctx.send(ws, {
12369
12735
  type: "brain.config",
12370
- payload: { config: ctx.brainRuntime.getSnapshot(), persisted: true }
12736
+ payload: { config: brainConfigPayload(ctx.brainRuntime), persisted: true }
12371
12737
  });
12372
12738
  }
12373
12739
  async function handleBrainConfigSet(ctx, ws, payload) {
@@ -12391,7 +12757,7 @@ async function handleBrainConfigSet(ctx, ws, payload) {
12391
12757
  ctx.send(ws, {
12392
12758
  type: "brain.config",
12393
12759
  payload: {
12394
- config: ctx.brainRuntime.getSnapshot(),
12760
+ config: brainConfigPayload(ctx.brainRuntime),
12395
12761
  persisted: result.ok,
12396
12762
  ...result.ok ? {} : { error: result.error ?? "Persist failed." }
12397
12763
  }
@@ -12401,7 +12767,7 @@ async function handleBrainConfigSet(ctx, ws, payload) {
12401
12767
  ctx.send(ws, {
12402
12768
  type: "brain.config",
12403
12769
  payload: {
12404
- config: ctx.brainRuntime.getSnapshot(),
12770
+ config: brainConfigPayload(ctx.brainRuntime),
12405
12771
  persisted: false,
12406
12772
  error: `Invalid Brain setting: ${toErrorMessage5(err)}`
12407
12773
  }
@@ -13166,7 +13532,7 @@ async function handleProcessRoute(ws, msg, handlers) {
13166
13532
  import * as fs14 from "node:fs/promises";
13167
13533
  import * as path16 from "node:path";
13168
13534
  import { DefaultSessionStore } from "@wrongstack/core/storage";
13169
- import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
13535
+ import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
13170
13536
  function createProjectHandlers(ctx) {
13171
13537
  const sendTo = (ws, message) => {
13172
13538
  if (ctx.sendMessage) ctx.sendMessage(ws, message);
@@ -13284,7 +13650,7 @@ function createProjectHandlers(ctx) {
13284
13650
  });
13285
13651
  return;
13286
13652
  }
13287
- const paths = resolveWstackPaths4({
13653
+ const paths = resolveWstackPaths5({
13288
13654
  projectRoot: resolved,
13289
13655
  globalRoot: ctx.wpaths.globalRoot
13290
13656
  });
@@ -13295,7 +13661,7 @@ function createProjectHandlers(ctx) {
13295
13661
  const previous = ctx.getSession();
13296
13662
  const previousId = previous.id;
13297
13663
  const previousProjectRoot = ctx.getProjectRoot();
13298
- const previousPaths = resolveWstackPaths4({
13664
+ const previousPaths = resolveWstackPaths5({
13299
13665
  projectRoot: previousProjectRoot,
13300
13666
  globalRoot: ctx.wpaths.globalRoot
13301
13667
  });
@@ -14218,6 +14584,7 @@ var CLIENT_CONVERSATION_MESSAGE_TYPES = [
14218
14584
  "modes.list",
14219
14585
  "session.checkpoints",
14220
14586
  "session.delete",
14587
+ "session.inspect",
14221
14588
  "session.new",
14222
14589
  "session.rename",
14223
14590
  "session.resume",
@@ -14481,6 +14848,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
14481
14848
  "session.checkpoints",
14482
14849
  "session.damaged",
14483
14850
  "session.end",
14851
+ "session.inspect",
14484
14852
  "session.rewound",
14485
14853
  "session.start",
14486
14854
  "session.stats",
@@ -14839,6 +15207,271 @@ function protocolAdvertisement() {
14839
15207
  }
14840
15208
 
14841
15209
  // src/server/session-history.ts
15210
+ function blockText(block) {
15211
+ if (typeof block === "string") return block;
15212
+ switch (block.type) {
15213
+ case "text":
15214
+ return block.text;
15215
+ case "thinking":
15216
+ return block.thinking;
15217
+ case "tool_use":
15218
+ return `[tool:${block.name}]`;
15219
+ case "tool_result":
15220
+ return block.content;
15221
+ case "image":
15222
+ return "[image]";
15223
+ default: {
15224
+ const _exhaustive = block;
15225
+ void _exhaustive;
15226
+ return "[block]";
15227
+ }
15228
+ }
15229
+ }
15230
+ function labelForEvent(e) {
15231
+ switch (e.type) {
15232
+ case "session_start":
15233
+ return "Session started";
15234
+ case "session_resumed":
15235
+ return "Session resumed";
15236
+ case "session_forked":
15237
+ return `Forked from ${e.parentSessionId}`;
15238
+ case "user_input": {
15239
+ const content = typeof e.content === "string" ? e.content : Array.isArray(e.content) ? e.content.map(blockText).join("") : "[input]";
15240
+ return `User: ${content.length > 100 ? content.slice(0, 100) + "\u2026" : content}`;
15241
+ }
15242
+ case "llm_request":
15243
+ return "LLM request";
15244
+ case "llm_response":
15245
+ return "LLM response";
15246
+ case "tool_use":
15247
+ return `Tool: ${e.name}`;
15248
+ case "tool_call_start":
15249
+ return `Tool start: ${e.name}`;
15250
+ case "tool_call_end":
15251
+ return `Tool done: ${e.name} (${e.durationMs}ms, ${e.ok === false ? "error" : "ok"})`;
15252
+ case "tool_result":
15253
+ return `Tool result: ${e.id}${e.isError ? " (error)" : ""}`;
15254
+ case "tool_progress":
15255
+ return `Progress: ${e.name} \u2014 ${e.event.type}`;
15256
+ case "compaction":
15257
+ return `Compaction: ${e.before} \u2192 ${e.after} tokens`;
15258
+ case "context_snapshot":
15259
+ return "Context snapshot";
15260
+ case "error":
15261
+ return `Error: ${e.message.length > 100 ? e.message.slice(0, 100) + "\u2026" : e.message}`;
15262
+ case "session_end":
15263
+ return "Session ended";
15264
+ case "message_appended":
15265
+ return `Message appended: ${e.message.role}`;
15266
+ case "message_updated":
15267
+ return "Message updated";
15268
+ case "messages_replaced": {
15269
+ const count = e.messagesOmitted ?? e.messages.length;
15270
+ return `Messages replaced (${e.messagesOmitted ? "~" : ""}${count} msgs)`;
15271
+ }
15272
+ case "message_truncated":
15273
+ return `Message truncated: ${e.before} \u2192 ${e.after}`;
15274
+ case "file_event":
15275
+ return `File ${e.operation}: ${e.filePath} (${e.toolName})`;
15276
+ case "file_snapshot":
15277
+ return `File snapshot at prompt #${e.promptIndex}`;
15278
+ case "file_observation":
15279
+ return `File observation: ${e.path}`;
15280
+ case "rewound":
15281
+ return `Rewound to prompt #${e.toPromptIndex} (${e.revertedFiles.length} files)`;
15282
+ case "mode_changed":
15283
+ return `Mode: ${e.from} \u2192 ${e.to}`;
15284
+ case "task_created":
15285
+ return `Task created: ${e.title}`;
15286
+ case "task_updated":
15287
+ return `Task ${e.taskId}: ${e.status}`;
15288
+ case "task_completed":
15289
+ return `Task done: ${e.title}`;
15290
+ case "task_failed":
15291
+ return `Task failed: ${e.title} \u2014 ${e.error.length > 80 ? e.error.slice(0, 80) + "\u2026" : e.error}`;
15292
+ case "agent_spawned":
15293
+ return `Agent spawned: ${e.role}`;
15294
+ case "agent_stopped":
15295
+ return "Agent stopped";
15296
+ case "agent_error":
15297
+ return `Agent error: ${e.error.length > 80 ? e.error.slice(0, 80) + "\u2026" : e.error}`;
15298
+ case "provider_retry":
15299
+ return `Retry: ${e.description} (attempt ${e.attempt})`;
15300
+ case "provider_error":
15301
+ return `Provider error: ${e.description}`;
15302
+ case "checkpoint":
15303
+ return `Checkpoint at prompt #${e.promptIndex}`;
15304
+ case "in_flight_start":
15305
+ return `In-flight started: ${e.context}`;
15306
+ case "in_flight_end":
15307
+ return `In-flight ended: ${e.reason}`;
15308
+ case "side_effect":
15309
+ return `Side effect: ${e.toolName} (${e.risk})`;
15310
+ case "spec_parsed":
15311
+ return `Spec parsed: ${e.title}`;
15312
+ case "spec_analyzed":
15313
+ return `Spec analyzed: ${e.specId}`;
15314
+ case "skill_activated":
15315
+ return `Skill: ${e.skillName}`;
15316
+ case "skill_deactivated":
15317
+ return `Skill done: ${e.skillName}`;
15318
+ default: {
15319
+ const _exhaustive = e;
15320
+ void _exhaustive;
15321
+ return String(_exhaustive);
15322
+ }
15323
+ }
15324
+ }
15325
+ function detailForEvent(e) {
15326
+ switch (e.type) {
15327
+ case "session_start":
15328
+ return `${e.model} @ ${e.provider}`;
15329
+ case "session_resumed":
15330
+ return `${e.model} @ ${e.provider}`;
15331
+ case "session_forked":
15332
+ return `parent checkpoint: ${e.parentCheckpointHash.slice(0, 12)}\u2026`;
15333
+ case "llm_request":
15334
+ return `${e.model} \xB7 ${e.messageCount} msgs \xB7 ${e.toolCount ?? "?"} tools`;
15335
+ case "llm_response":
15336
+ return `${e.stopReason} \xB7 ${e.usage.input ?? 0}+${e.usage.output ?? 0} tokens`;
15337
+ case "tool_use":
15338
+ return `id: ${e.id}`;
15339
+ case "tool_call_start":
15340
+ return `id: ${e.id}`;
15341
+ case "tool_call_end":
15342
+ return `${(e.outputBytes ?? e.outputSize ?? 0).toLocaleString()} B \xB7 ${e.outputLines ?? 0} lines`;
15343
+ case "tool_progress":
15344
+ return `${e.event.type}${e.event.text ? `: ${e.event.text}` : ""}`;
15345
+ case "compaction":
15346
+ return `saved ~${Math.max(0, e.before - e.after)} tokens`;
15347
+ case "context_snapshot":
15348
+ return `${e.messages.length} msgs${e.messagesOmitted ? ` (${e.messagesOmitted} omitted)` : ""}`;
15349
+ case "error":
15350
+ return `phase: ${e.phase}`;
15351
+ case "session_end":
15352
+ return `${e.usage.input ?? 0}+${e.usage.output ?? 0} total tokens`;
15353
+ case "message_appended":
15354
+ return `appended ${e.message.role}`;
15355
+ case "message_updated":
15356
+ return `at index ${e.index}`;
15357
+ case "messages_replaced":
15358
+ return `${e.messagesOmitted ?? e.messages.length} total`;
15359
+ case "message_truncated":
15360
+ return `truncated to ${e.after} tokens`;
15361
+ case "mode_changed":
15362
+ return `${e.from} \u2192 ${e.to}`;
15363
+ case "task_created":
15364
+ return `id: ${e.taskId}`;
15365
+ case "task_updated":
15366
+ return `${e.taskId}: ${e.status}`;
15367
+ case "task_completed":
15368
+ return `${e.taskId}`;
15369
+ case "task_failed":
15370
+ return `${e.taskId}: ${e.error}`;
15371
+ case "agent_spawned":
15372
+ return `id: ${e.agentId} (${e.role})`;
15373
+ case "agent_stopped":
15374
+ return `id: ${e.agentId}`;
15375
+ case "agent_error":
15376
+ return `id: ${e.agentId}: ${e.error}`;
15377
+ case "file_event":
15378
+ return `${e.filePath} (${e.toolName})`;
15379
+ case "file_snapshot":
15380
+ return `${e.files.length} files at prompt #${e.promptIndex}`;
15381
+ case "file_observation":
15382
+ return `${e.source === "user" ? "user-saved" : "tool-written"} at ${e.path}`;
15383
+ case "rewound":
15384
+ return `${e.revertedFiles.length} files reverted`;
15385
+ case "in_flight_start":
15386
+ return e.context;
15387
+ case "in_flight_end":
15388
+ return `${e.reason}`;
15389
+ case "side_effect":
15390
+ return `${e.toolName} (${e.risk})${e.outcome ? `: ${e.outcome}` : ""}`;
15391
+ case "provider_retry":
15392
+ return `delay ${e.delayMs}ms${e.status ? ` \xB7 HTTP ${e.status}` : ""}`;
15393
+ case "provider_error":
15394
+ return `${e.retryable ? "retryable" : "fatal"}${e.status ? ` \xB7 HTTP ${e.status}` : ""}`;
15395
+ case "user_input":
15396
+ return typeof e.content === "string" ? `${e.content.length} chars` : `${e.content.length} blocks`;
15397
+ case "tool_result":
15398
+ return `${e.isError ? "error" : "ok"}`;
15399
+ case "checkpoint":
15400
+ return `prompt #${e.promptIndex}`;
15401
+ case "spec_parsed":
15402
+ return `${e.title} (${e.completeness}% complete)`;
15403
+ case "spec_analyzed":
15404
+ return `${e.gaps.length} gaps identified`;
15405
+ case "skill_activated":
15406
+ return `at ${e.skillName}`;
15407
+ case "skill_deactivated":
15408
+ return `at ${e.skillName}`;
15409
+ default: {
15410
+ const _exhaustive = e;
15411
+ void _exhaustive;
15412
+ return "";
15413
+ }
15414
+ }
15415
+ }
15416
+ function buildInspectPayload(summary, events, fallback) {
15417
+ const inspectEvents = events.map((e) => ({
15418
+ ts: e.ts,
15419
+ type: e.type,
15420
+ label: labelForEvent(e),
15421
+ detail: detailForEvent(e)
15422
+ }));
15423
+ const fileEvents = [];
15424
+ let computedToolCallCount = 0;
15425
+ let computedToolErrorCount = 0;
15426
+ let computedFileChangeCount = 0;
15427
+ let computedCompactionCount = 0;
15428
+ let computedMessageCount = 0;
15429
+ let computedIterationCount = 0;
15430
+ const computedToolBreakdown = {};
15431
+ for (const e of events) {
15432
+ if (e.type === "file_event") {
15433
+ fileEvents.push({
15434
+ operation: e.operation,
15435
+ filePath: e.filePath,
15436
+ toolName: e.toolName,
15437
+ ts: e.ts
15438
+ });
15439
+ computedFileChangeCount++;
15440
+ } else if (e.type === "tool_call_end") {
15441
+ computedToolCallCount++;
15442
+ if (e.ok === false) computedToolErrorCount++;
15443
+ computedToolBreakdown[e.name] = (computedToolBreakdown[e.name] ?? 0) + 1;
15444
+ } else if (e.type === "compaction") {
15445
+ computedCompactionCount++;
15446
+ } else if (e.type === "user_input") {
15447
+ computedMessageCount++;
15448
+ } else if (e.type === "llm_response") {
15449
+ computedIterationCount++;
15450
+ }
15451
+ }
15452
+ const s = summary;
15453
+ return {
15454
+ id: s?.id ?? fallback.id,
15455
+ title: s?.title ?? fallback.title,
15456
+ ...s?.name !== void 0 ? { name: s.name } : {},
15457
+ model: s?.model ?? fallback.model,
15458
+ provider: s?.provider ?? fallback.provider,
15459
+ startedAt: s?.startedAt ?? fallback.startedAt,
15460
+ ...(s?.endedAt ?? fallback.endedAt) !== void 0 ? { endedAt: s?.endedAt ?? fallback.endedAt } : {},
15461
+ tokenTotal: s?.tokenTotal ?? 0,
15462
+ ...s?.outcome !== void 0 ? { outcome: s.outcome } : {},
15463
+ messageCount: s?.messageCount ?? computedMessageCount,
15464
+ iterationCount: s?.iterationCount ?? computedIterationCount,
15465
+ toolCallCount: s?.toolCallCount ?? computedToolCallCount,
15466
+ toolErrorCount: s?.toolErrorCount ?? computedToolErrorCount,
15467
+ fileChangeCount: s?.fileChangeCount ?? computedFileChangeCount,
15468
+ compactionCount: s?.compactionCount ?? computedCompactionCount,
15469
+ toolBreakdown: s?.toolBreakdown ?? computedToolBreakdown,
15470
+ events: inspectEvents,
15471
+ fileEvents,
15472
+ ...s?.lastUserMessage !== void 0 ? { lastUserMessage: s.lastUserMessage } : {}
15473
+ };
15474
+ }
14842
15475
  function toSessionHistoryEntry(summary, currentSessionId2) {
14843
15476
  return {
14844
15477
  id: summary.id,
@@ -15385,6 +16018,47 @@ function createSessionHandlers(ctx) {
15385
16018
  if (!ensureCurrentSession(ws, msg, "session.save")) return;
15386
16019
  result(ws, true, `Session ${ctx.getSession().id} is auto-saved`);
15387
16020
  },
16021
+ inspectSession: async (ws, msg) => {
16022
+ const { id } = msg.payload;
16023
+ if (!id) {
16024
+ sendTo(ws, {
16025
+ type: "session.inspect",
16026
+ payload: { id: "", error: "Session id is required" }
16027
+ });
16028
+ return;
16029
+ }
16030
+ try {
16031
+ const store = ctx.getSessionStore();
16032
+ const data = await store.load(id);
16033
+ let summary;
16034
+ try {
16035
+ const summaries = await store.list(200);
16036
+ summary = summaries.find((s) => s.id === id);
16037
+ } catch {
16038
+ summary = void 0;
16039
+ }
16040
+ const payload = buildInspectPayload(summary, data.events, {
16041
+ id: data.metadata.id,
16042
+ title: data.metadata.title ?? "",
16043
+ model: data.metadata.model ?? "",
16044
+ provider: data.metadata.provider ?? "",
16045
+ startedAt: data.metadata.startedAt,
16046
+ endedAt: data.metadata.endedAt
16047
+ });
16048
+ sendTo(ws, {
16049
+ type: "session.inspect",
16050
+ payload
16051
+ });
16052
+ } catch (err) {
16053
+ sendTo(ws, {
16054
+ type: "session.inspect",
16055
+ payload: {
16056
+ id,
16057
+ error: err instanceof Error ? err.message : String(err)
16058
+ }
16059
+ });
16060
+ }
16061
+ },
15388
16062
  listCheckpoints: async (ws, msg) => {
15389
16063
  if (!ensureCurrentSession(ws, msg, "session.checkpoints")) return;
15390
16064
  try {
@@ -16144,7 +16818,7 @@ async function handleProviderRoute(ws, msg, routes) {
16144
16818
  const customModelRaw = payload["customModel"];
16145
16819
  if (!isRecord4(customModelRaw)) return invalidPayload(ws, msg.type);
16146
16820
  const cm = optionalCustomModels({ customModels: { [modelId]: customModelRaw } });
16147
- if (!cm || !cm[modelId]) return invalidPayload(ws, msg.type);
16821
+ if (!cm?.[modelId]) return invalidPayload(ws, msg.type);
16148
16822
  await routes.providerHandlers.handleCustomModelSet(ws, providerId, modelId, cm[modelId]);
16149
16823
  return true;
16150
16824
  }
@@ -16338,6 +17012,9 @@ async function handleSessionRoute(ws, msg, handlers) {
16338
17012
  case "session.save":
16339
17013
  await handlers.saveSession(ws, msg);
16340
17014
  return true;
17015
+ case "session.inspect":
17016
+ await handlers.inspectSession(ws, msg);
17017
+ return true;
16341
17018
  case "session.checkpoints":
16342
17019
  await handlers.listCheckpoints(ws, msg);
16343
17020
  return true;
@@ -18210,7 +18887,8 @@ function setupEvents(deps2) {
18210
18887
  attempt: e.attempt,
18211
18888
  delayMs: e.delayMs,
18212
18889
  status: e.status,
18213
- description: e.description
18890
+ description: e.description,
18891
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
18214
18892
  })
18215
18893
  });
18216
18894
  appendForCurrentSession(e.sessionId, {
@@ -18220,7 +18898,8 @@ function setupEvents(deps2) {
18220
18898
  attempt: e.attempt,
18221
18899
  delayMs: e.delayMs,
18222
18900
  status: e.status,
18223
- description: e.description
18901
+ description: e.description,
18902
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
18224
18903
  });
18225
18904
  });
18226
18905
  on("provider.status_changed", (e) => {
@@ -18260,7 +18939,8 @@ function setupEvents(deps2) {
18260
18939
  providerId: e.providerId,
18261
18940
  status: e.status,
18262
18941
  description: e.description,
18263
- retryable: e.retryable
18942
+ retryable: e.retryable,
18943
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
18264
18944
  })
18265
18945
  });
18266
18946
  appendForCurrentSession(e.sessionId, {
@@ -18269,7 +18949,8 @@ function setupEvents(deps2) {
18269
18949
  providerId: e.providerId,
18270
18950
  status: e.status,
18271
18951
  description: e.description,
18272
- retryable: e.retryable
18952
+ retryable: e.retryable,
18953
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
18273
18954
  });
18274
18955
  });
18275
18956
  on("provider.fallback", (e) => {
@@ -21346,7 +22027,7 @@ import { makeProviderFromConfig as makeProviderFromConfig2, withCatalogCapabilit
21346
22027
 
21347
22028
  // src/server/mode-handlers.ts
21348
22029
  import { DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2 } from "@wrongstack/core/agent";
21349
- import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
22030
+ import { resolveWstackPaths as resolveWstackPaths6 } from "@wrongstack/core/utils";
21350
22031
  function createModeHandlers(context) {
21351
22032
  return createModeRouteHandlers({
21352
22033
  modeStore: context.modeStore,
@@ -21355,7 +22036,7 @@ function createModeHandlers(context) {
21355
22036
  send,
21356
22037
  afterSwitch: async (id) => {
21357
22038
  const modePrompt = id === "default" ? "" : (await context.modeStore.getMode(id))?.prompt ?? "";
21358
- const paths = resolveWstackPaths5({
22039
+ const paths = resolveWstackPaths6({
21359
22040
  projectRoot: context.projectRoot,
21360
22041
  globalRoot: context.globalRoot
21361
22042
  });
@@ -21715,7 +22396,10 @@ async function resolvePorts(opts) {
21715
22396
  const surface = opts.surface ?? "webui";
21716
22397
  const surfaceDefaults = surface === "simpleui" ? { http: 3466 } : { http: 3456 };
21717
22398
  const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
21718
- const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
22399
+ const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(
22400
+ process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http),
22401
+ 10
22402
+ );
21719
22403
  const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
21720
22404
  const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
21721
22405
  const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
@@ -21724,7 +22408,16 @@ async function resolvePorts(opts) {
21724
22408
  if (!strictPort) {
21725
22409
  httpPort = await findFreePort(wsHost, requestedHttpPort);
21726
22410
  if (httpPort !== requestedHttpPort) {
21727
- console.warn(JSON.stringify({ level: "warn", event: "webui.port_reassigned", protocol: "HTTP", requested: requestedHttpPort, assigned: httpPort, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
22411
+ console.warn(
22412
+ JSON.stringify({
22413
+ level: "warn",
22414
+ event: "webui.port_reassigned",
22415
+ protocol: "HTTP",
22416
+ requested: requestedHttpPort,
22417
+ assigned: httpPort,
22418
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
22419
+ })
22420
+ );
21728
22421
  }
21729
22422
  }
21730
22423
  return { wsHost, httpPort, publicUrl, publicWsUrl, requireToken };
@@ -21830,21 +22523,49 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
21830
22523
  if (eventsArmed) return;
21831
22524
  eventsArmed = true;
21832
22525
  console.log(`[WebUI] Backend ready (${label})`);
21833
- disposeEvents = setupEvents({ ...setupInput, watcherMetrics, onFleetBroadcaster: (fn) => {
21834
- fleetBroadcast = fn;
21835
- } });
22526
+ disposeEvents = setupEvents({
22527
+ ...setupInput,
22528
+ watcherMetrics,
22529
+ onFleetBroadcaster: (fn) => {
22530
+ fleetBroadcast = fn;
22531
+ }
22532
+ });
21836
22533
  };
21837
22534
  wssPrimary.on("listening", () => arm(`${wsHost}:${httpPort}`));
21838
22535
  wssPrimary.on("error", (err) => {
21839
- console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage12(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
22536
+ console.error(
22537
+ JSON.stringify({
22538
+ level: "error",
22539
+ event: "webui.ws_server_error",
22540
+ host: wsHost,
22541
+ message: toErrorMessage12(err),
22542
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
22543
+ })
22544
+ );
21840
22545
  });
21841
22546
  if (wssSecondary) {
21842
22547
  wssSecondary.on("listening", () => arm(`::1:${httpPort}`));
21843
22548
  wssSecondary.on("error", (err) => {
21844
22549
  if (err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL") {
21845
- console.warn(JSON.stringify({ level: "warn", event: "webui.ipv6_unavailable", code: err.code, message: err.message, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
22550
+ console.warn(
22551
+ JSON.stringify({
22552
+ level: "warn",
22553
+ event: "webui.ipv6_unavailable",
22554
+ code: err.code,
22555
+ message: err.message,
22556
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
22557
+ })
22558
+ );
21846
22559
  } else {
21847
- console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: "::1", message: err.message, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
22560
+ console.error(
22561
+ JSON.stringify({
22562
+ level: "error",
22563
+ event: "webui.ws_server_error",
22564
+ host: "::1",
22565
+ message: err.message,
22566
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
22567
+ })
22568
+ );
21848
22569
  }
21849
22570
  });
21850
22571
  }
@@ -21865,6 +22586,7 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
21865
22586
  }
21866
22587
  }
21867
22588
  function startHttpServer(opts) {
22589
+ const intakeService = opts.intakeService ?? createProjectIntakeService({ projectRoot: opts.projectRoot, globalRoot: opts.globalRoot });
21868
22590
  const httpServer = createHttpServer({
21869
22591
  host: opts.wsHost,
21870
22592
  port: opts.httpPort,
@@ -21878,7 +22600,8 @@ function startHttpServer(opts) {
21878
22600
  onTechStackEvent: opts.onTechStackEvent,
21879
22601
  getLlm: opts.getLlm,
21880
22602
  executePackageOperation: opts.executePackageOperation,
21881
- projectRoot: opts.projectRoot
22603
+ projectRoot: opts.projectRoot,
22604
+ intakeService
21882
22605
  });
21883
22606
  return httpServer;
21884
22607
  }