@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.
package/dist/index.js CHANGED
@@ -8094,7 +8094,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
8094
8094
  }
8095
8095
  try {
8096
8096
  const { SessionRegistry, DefaultSessionStore: DefaultSessionStore4, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core/storage");
8097
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
8097
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
8098
8098
  const registry = new SessionRegistry(globalRoot);
8099
8099
  const entry = await registry.get(sessionId);
8100
8100
  if (!entry) {
@@ -8102,7 +8102,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
8102
8102
  res.end(JSON.stringify({ error: "Session not found" }));
8103
8103
  return;
8104
8104
  }
8105
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
8105
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
8106
8106
  const store = new DefaultSessionStore4({ dir: paths.projectSessions });
8107
8107
  const reader = new DefaultSessionReader2({ store });
8108
8108
  const rawEntries = [];
@@ -8183,7 +8183,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
8183
8183
  try {
8184
8184
  const { SessionRegistry } = await import("@wrongstack/core/storage");
8185
8185
  const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
8186
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
8186
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
8187
8187
  const registry = new SessionRegistry(globalRoot);
8188
8188
  const entry = await registry.get(sessionId);
8189
8189
  if (!entry) {
@@ -8191,7 +8191,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
8191
8191
  res.end(JSON.stringify({ error: "Session not found" }));
8192
8192
  return;
8193
8193
  }
8194
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
8194
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
8195
8195
  const mailbox = getSharedProjectMailbox6(paths.projectDir);
8196
8196
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
8197
8197
  const sent = await mailbox.send({ from, to, type, subject, body: text2, priority });
@@ -8211,7 +8211,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
8211
8211
  try {
8212
8212
  const { SessionRegistry } = await import("@wrongstack/core/storage");
8213
8213
  const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
8214
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
8214
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
8215
8215
  const registry = new SessionRegistry(globalRoot);
8216
8216
  const entry = await registry.get(sessionId);
8217
8217
  if (!entry) {
@@ -8219,7 +8219,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
8219
8219
  res.end(JSON.stringify({ error: "Session not found" }));
8220
8220
  return;
8221
8221
  }
8222
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
8222
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
8223
8223
  const mailbox = getSharedProjectMailbox6(paths.projectDir);
8224
8224
  const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
8225
8225
  const [inbound, outbound] = await Promise.all([
@@ -8271,7 +8271,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
8271
8271
  try {
8272
8272
  const { SessionRegistry } = await import("@wrongstack/core/storage");
8273
8273
  const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
8274
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
8274
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
8275
8275
  const registry = new SessionRegistry(globalRoot);
8276
8276
  const entry = await registry.get(sessionId);
8277
8277
  if (!entry) {
@@ -8279,7 +8279,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
8279
8279
  res.end(JSON.stringify({ error: "Session not found" }));
8280
8280
  return;
8281
8281
  }
8282
- const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
8282
+ const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
8283
8283
  const mailbox = getSharedProjectMailbox6(paths.projectDir);
8284
8284
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
8285
8285
  const sent = await mailbox.sendRuntimeControl({
@@ -8320,7 +8320,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
8320
8320
  try {
8321
8321
  const { SessionRegistry } = await import("@wrongstack/core/storage");
8322
8322
  const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
8323
- const { resolveWstackPaths: resolveWstackPaths6 } = await import("@wrongstack/core/utils");
8323
+ const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
8324
8324
  const registry = new SessionRegistry(globalRoot);
8325
8325
  const all = await registry.list();
8326
8326
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -8332,7 +8332,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
8332
8332
  }
8333
8333
  const mbByDir = /* @__PURE__ */ new Map();
8334
8334
  const mailboxFor = (projectRoot) => {
8335
- const dir = resolveWstackPaths6({ projectRoot, globalRoot }).projectDir;
8335
+ const dir = resolveWstackPaths7({ projectRoot, globalRoot }).projectDir;
8336
8336
  let mb = mbByDir.get(dir);
8337
8337
  if (!mb) {
8338
8338
  mb = getSharedProjectMailbox6(dir);
@@ -8366,6 +8366,246 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
8366
8366
  }
8367
8367
  }
8368
8368
 
8369
+ // src/server/requirement-intake-handlers.ts
8370
+ import { readProjectIdentity } from "@wrongstack/core/utils";
8371
+ import {
8372
+ IntakeError,
8373
+ IntakeValidationError
8374
+ } from "@wrongstack/requirement-intake";
8375
+ var SERVER_ACTOR = { id: "webui-server", type: "agent" };
8376
+ var MAX_INTAKE_BODY_BYTES = 512e3;
8377
+ function intakeContext(projectId) {
8378
+ return { ...SERVER_ACTOR, projectId };
8379
+ }
8380
+ function sendJson2(res, status, body) {
8381
+ res.writeHead(status, { "Content-Type": "application/json" });
8382
+ res.end(JSON.stringify(body));
8383
+ }
8384
+ function sendNotFound(res, intakeId) {
8385
+ sendJson2(res, 404, {
8386
+ error: {
8387
+ code: "INTAKE_NOT_FOUND",
8388
+ message: `Requirement intake record not found: ${intakeId}`
8389
+ }
8390
+ });
8391
+ }
8392
+ function sendIntakeError(res, error2) {
8393
+ if (error2 instanceof IntakeValidationError) {
8394
+ sendJson2(res, 400, {
8395
+ error: { code: error2.code, message: error2.message, issues: error2.issues }
8396
+ });
8397
+ return;
8398
+ }
8399
+ if (error2 instanceof IntakeError) {
8400
+ const status = error2.code === "INTAKE_UNAUTHORIZED" ? 403 : error2.code === "INTAKE_NOT_FOUND" ? 404 : error2.code === "INTAKE_SUGGESTION_ERROR" ? 502 : 409;
8401
+ sendJson2(res, status, { error: { code: error2.code, message: error2.message } });
8402
+ return;
8403
+ }
8404
+ sendJson2(res, 500, { error: { code: "INTERNAL_ERROR", message: "Internal server error" } });
8405
+ }
8406
+ function serviceOr503(res, service) {
8407
+ if (service) return true;
8408
+ sendJson2(res, 503, {
8409
+ error: { code: "INTAKE_UNAVAILABLE", message: "Requirement intake service not configured" }
8410
+ });
8411
+ return false;
8412
+ }
8413
+ async function readJsonBody3(res, req) {
8414
+ const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
8415
+ if (contentType !== "application/json") {
8416
+ sendJson2(res, 400, {
8417
+ error: {
8418
+ code: "INVALID_CONTENT_TYPE",
8419
+ message: `Unsupported Content-Type: ${contentType || "(absent)"}`
8420
+ }
8421
+ });
8422
+ return null;
8423
+ }
8424
+ return new Promise((resolve16) => {
8425
+ let data = "";
8426
+ let failed = false;
8427
+ const fail2 = (message) => {
8428
+ if (failed) return;
8429
+ failed = true;
8430
+ sendJson2(res, 400, { error: { code: "INVALID_BODY", message } });
8431
+ resolve16(null);
8432
+ };
8433
+ req.on("data", (chunk) => {
8434
+ if (failed) return;
8435
+ data += chunk.toString("utf8");
8436
+ if (data.length > MAX_INTAKE_BODY_BYTES) {
8437
+ req.destroy();
8438
+ fail2("Request body too large");
8439
+ }
8440
+ });
8441
+ req.on("end", () => {
8442
+ if (failed) return;
8443
+ try {
8444
+ resolve16(data.trim().length === 0 ? {} : JSON.parse(data));
8445
+ } catch {
8446
+ fail2("Request body is not valid JSON");
8447
+ }
8448
+ });
8449
+ req.on("error", () => fail2("Failed to read request body"));
8450
+ });
8451
+ }
8452
+ async function discoverIntake(res, service, intakeId) {
8453
+ const record2 = await service.getIntake(intakeId, intakeContext(""));
8454
+ if (!record2) {
8455
+ sendNotFound(res, intakeId);
8456
+ return null;
8457
+ }
8458
+ return record2;
8459
+ }
8460
+ async function handleRequirementIntakeListForServer(res, service, projectRoot) {
8461
+ if (!serviceOr503(res, service)) return;
8462
+ if (!projectRoot) {
8463
+ sendJson2(res, 503, {
8464
+ error: { code: "PROJECT_ROOT_NOT_CONFIGURED", message: "Project root not configured" }
8465
+ });
8466
+ return;
8467
+ }
8468
+ try {
8469
+ const identity = await readProjectIdentity(projectRoot);
8470
+ if (!identity) {
8471
+ sendJson2(res, 404, {
8472
+ error: {
8473
+ code: "PROJECT_IDENTITY_NOT_FOUND",
8474
+ message: "No project identity found \u2014 run `wstack init` first"
8475
+ }
8476
+ });
8477
+ return;
8478
+ }
8479
+ const projectId = identity.projectId;
8480
+ const records = await service.listIntakes(projectId, intakeContext(projectId));
8481
+ sendJson2(res, 200, { projectId, intakes: records });
8482
+ } catch (error2) {
8483
+ sendIntakeError(res, error2);
8484
+ }
8485
+ }
8486
+ async function handleRequirementIntakeCreate(res, req, service, projectId) {
8487
+ if (!serviceOr503(res, service)) return;
8488
+ const body = await readJsonBody3(res, req);
8489
+ if (body === null) return;
8490
+ try {
8491
+ const result = await service.createIntake(body, intakeContext(projectId));
8492
+ sendJson2(res, result.idempotent ? 200 : 201, result);
8493
+ } catch (error2) {
8494
+ sendIntakeError(res, error2);
8495
+ }
8496
+ }
8497
+ async function handleRequirementIntakeList(res, service, projectId) {
8498
+ if (!serviceOr503(res, service)) return;
8499
+ try {
8500
+ const records = await service.listIntakes(projectId, intakeContext(projectId));
8501
+ sendJson2(res, 200, records);
8502
+ } catch (error2) {
8503
+ sendIntakeError(res, error2);
8504
+ }
8505
+ }
8506
+ async function handleRequirementIntakeGet(res, service, intakeId) {
8507
+ if (!serviceOr503(res, service)) return;
8508
+ try {
8509
+ const record2 = await discoverIntake(res, service, intakeId);
8510
+ if (!record2) return;
8511
+ sendJson2(res, 200, record2);
8512
+ } catch (error2) {
8513
+ sendIntakeError(res, error2);
8514
+ }
8515
+ }
8516
+ async function handleRequirementIntakeUpdate(res, req, service, intakeId) {
8517
+ if (!serviceOr503(res, service)) return;
8518
+ const body = await readJsonBody3(res, req);
8519
+ if (body === null) return;
8520
+ try {
8521
+ const record2 = await discoverIntake(res, service, intakeId);
8522
+ if (!record2) return;
8523
+ const expectedVersion = typeof body["expectedVersion"] === "number" ? body["expectedVersion"] : void 0;
8524
+ const { expectedVersion: _ignored, ...patch } = body;
8525
+ void _ignored;
8526
+ const updated = await service.updateIntake(
8527
+ intakeId,
8528
+ patch,
8529
+ intakeContext(record2.projectId),
8530
+ expectedVersion
8531
+ );
8532
+ sendJson2(res, 200, updated);
8533
+ } catch (error2) {
8534
+ sendIntakeError(res, error2);
8535
+ }
8536
+ }
8537
+ async function handleRequirementIntakeAnswers(res, req, service, intakeId) {
8538
+ if (!serviceOr503(res, service)) return;
8539
+ const body = await readJsonBody3(res, req);
8540
+ if (body === null) return;
8541
+ try {
8542
+ const record2 = await discoverIntake(res, service, intakeId);
8543
+ if (!record2) return;
8544
+ const updated = await service.addAnswer(
8545
+ intakeId,
8546
+ body,
8547
+ intakeContext(record2.projectId)
8548
+ );
8549
+ sendJson2(res, 200, updated);
8550
+ } catch (error2) {
8551
+ sendIntakeError(res, error2);
8552
+ }
8553
+ }
8554
+ async function handleRequirementIntakeSuggestions(res, req, service, intakeId) {
8555
+ if (!serviceOr503(res, service)) return;
8556
+ const body = await readJsonBody3(res, req);
8557
+ if (body === null) return;
8558
+ try {
8559
+ const record2 = await discoverIntake(res, service, intakeId);
8560
+ if (!record2) return;
8561
+ const focus = Array.isArray(body["focus"]) ? body["focus"] : void 0;
8562
+ const suggestions = await service.generateSuggestions(
8563
+ intakeId,
8564
+ intakeContext(record2.projectId),
8565
+ focus
8566
+ );
8567
+ sendJson2(res, 200, { suggestions });
8568
+ } catch (error2) {
8569
+ sendIntakeError(res, error2);
8570
+ }
8571
+ }
8572
+ async function handleRequirementIntakeSubmit(res, service, intakeId) {
8573
+ if (!serviceOr503(res, service)) return;
8574
+ try {
8575
+ const record2 = await discoverIntake(res, service, intakeId);
8576
+ if (!record2) return;
8577
+ const result = await service.submitIntake(intakeId, intakeContext(record2.projectId));
8578
+ sendJson2(res, 200, result);
8579
+ } catch (error2) {
8580
+ sendIntakeError(res, error2);
8581
+ }
8582
+ }
8583
+ async function handleRequirementIntakeCancel(res, req, service, intakeId) {
8584
+ if (!serviceOr503(res, service)) return;
8585
+ const body = await readJsonBody3(res, req);
8586
+ if (body === null) return;
8587
+ try {
8588
+ const record2 = await discoverIntake(res, service, intakeId);
8589
+ if (!record2) return;
8590
+ const reason = typeof body["reason"] === "string" ? body["reason"] : void 0;
8591
+ const updated = await service.cancelIntake(intakeId, intakeContext(record2.projectId), reason);
8592
+ sendJson2(res, 200, updated);
8593
+ } catch (error2) {
8594
+ sendIntakeError(res, error2);
8595
+ }
8596
+ }
8597
+ async function handleRequirementIntakeArchive(res, service, intakeId) {
8598
+ if (!serviceOr503(res, service)) return;
8599
+ try {
8600
+ const record2 = await discoverIntake(res, service, intakeId);
8601
+ if (!record2) return;
8602
+ const updated = await service.archiveIntake(intakeId, intakeContext(record2.projectId));
8603
+ sendJson2(res, 200, updated);
8604
+ } catch (error2) {
8605
+ sendIntakeError(res, error2);
8606
+ }
8607
+ }
8608
+
8369
8609
  // src/server/memory-diagnostics.ts
8370
8610
  import * as fs8 from "node:fs/promises";
8371
8611
  import * as path11 from "node:path";
@@ -8571,7 +8811,7 @@ async function touchProjectInManifest(options, globalConfigPath) {
8571
8811
  // src/server/techstack-handlers.ts
8572
8812
  import { randomUUID as randomUUID2 } from "node:crypto";
8573
8813
  var DEEP_DIVE_TIMEOUT_MS = 6e4;
8574
- function sendJson2(res, status, data) {
8814
+ function sendJson3(res, status, data) {
8575
8815
  res.writeHead(status, { "Content-Type": "application/json" });
8576
8816
  res.end(JSON.stringify(data));
8577
8817
  }
@@ -8586,13 +8826,13 @@ function handleTechStackSnapshot(res, deps2) {
8586
8826
  try {
8587
8827
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8588
8828
  if (!snapshot) {
8589
- sendJson2(res, 404, { snapshot: null, stale: false });
8829
+ sendJson3(res, 404, { snapshot: null, stale: false });
8590
8830
  return;
8591
8831
  }
8592
8832
  const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
8593
- sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
8833
+ sendJson3(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
8594
8834
  } catch (error2) {
8595
- sendJson2(res, 500, {
8835
+ sendJson3(res, 500, {
8596
8836
  error: "TechStack store unavailable",
8597
8837
  detail: errorMessage(error2)
8598
8838
  });
@@ -8603,7 +8843,7 @@ function errorMessage(error2) {
8603
8843
  }
8604
8844
  function requireJobDeps(res, deps2) {
8605
8845
  if (!deps2.projectRoot || !deps2.engine) {
8606
- sendJson2(res, 503, { error: "TechStack engine unavailable" });
8846
+ sendJson3(res, 503, { error: "TechStack engine unavailable" });
8607
8847
  return false;
8608
8848
  }
8609
8849
  return true;
@@ -8614,7 +8854,7 @@ function startJob(res, deps2, kind) {
8614
8854
  const controller = new AbortController();
8615
8855
  deps2.runningJobs?.set(jobId, controller);
8616
8856
  deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
8617
- sendJson2(res, 202, { jobId, kind, status: "queued" });
8857
+ sendJson3(res, 202, { jobId, kind, status: "queued" });
8618
8858
  void buildResearcher(deps2, kind).catch(() => void 0).then(
8619
8859
  (researcher) => deps2.engine.analyze(deps2.projectId, {
8620
8860
  targetRoot: deps2.projectRoot,
@@ -8660,24 +8900,24 @@ function handleTechStackCancel(res, deps2, jobId) {
8660
8900
  if (controller && !controller.signal.aborted) controller.abort();
8661
8901
  deps2.store.updateJobStatus(jobId, "cancelled");
8662
8902
  deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
8663
- sendJson2(res, 200, { jobId, status: "cancelled" });
8903
+ sendJson3(res, 200, { jobId, status: "cancelled" });
8664
8904
  }
8665
8905
  async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
8666
8906
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8667
8907
  const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
8668
8908
  if (!dependency) {
8669
- sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
8909
+ sendJson3(res, 404, { error: "Dependency not found in the current snapshot" });
8670
8910
  return;
8671
8911
  }
8672
8912
  let researcher;
8673
8913
  try {
8674
8914
  researcher = await buildResearcher(deps2, "analyze");
8675
8915
  } catch (error2) {
8676
- sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error2) });
8916
+ sendJson3(res, 503, { error: "Research unavailable", detail: errorMessage(error2) });
8677
8917
  return;
8678
8918
  }
8679
8919
  if (!researcher) {
8680
- sendJson2(res, 503, {
8920
+ sendJson3(res, 503, {
8681
8921
  error: "No model configured \u2014 connect a provider to run LLM analysis."
8682
8922
  });
8683
8923
  return;
@@ -8694,9 +8934,9 @@ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
8694
8934
  [triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
8695
8935
  { signal: controller.signal }
8696
8936
  );
8697
- sendJson2(res, 200, { dependencyId, findings });
8937
+ sendJson3(res, 200, { dependencyId, findings });
8698
8938
  } catch (error2) {
8699
- sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error2) });
8939
+ sendJson3(res, 500, { error: "Research failed", detail: errorMessage(error2) });
8700
8940
  } finally {
8701
8941
  clearTimeout(timeout);
8702
8942
  controller.abort();
@@ -8705,15 +8945,15 @@ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
8705
8945
  function handleTechStackJobStatus(res, deps2, jobId) {
8706
8946
  const job = deps2.store.getJob(jobId);
8707
8947
  if (!job) {
8708
- sendJson2(res, 404, { error: "Job not found" });
8948
+ sendJson3(res, 404, { error: "Job not found" });
8709
8949
  return;
8710
8950
  }
8711
- sendJson2(res, 200, { job });
8951
+ sendJson3(res, 200, { job });
8712
8952
  }
8713
8953
  function handleTechStackReport(res, deps2, reportId, format) {
8714
8954
  const snapshot = deps2.store.getSnapshotById(reportId);
8715
8955
  if (!snapshot) {
8716
- sendJson2(res, 404, { error: "Report not found" });
8956
+ sendJson3(res, 404, { error: "Report not found" });
8717
8957
  return;
8718
8958
  }
8719
8959
  if (deps2.engine) {
@@ -8724,30 +8964,30 @@ function handleTechStackReport(res, deps2, reportId, format) {
8724
8964
  });
8725
8965
  res.end(report);
8726
8966
  } else {
8727
- sendJson2(res, 200, snapshot);
8967
+ sendJson3(res, 200, snapshot);
8728
8968
  }
8729
8969
  }
8730
8970
  async function handleTechStackTrends(res, deps2) {
8731
8971
  try {
8732
8972
  const { TrendStore } = await import("@wrongstack/techstack");
8733
- sendJson2(res, 200, { trend: new TrendStore(deps2.store).analyze(deps2.projectId) });
8973
+ sendJson3(res, 200, { trend: new TrendStore(deps2.store).analyze(deps2.projectId) });
8734
8974
  } catch (error2) {
8735
- sendJson2(res, 500, { error: "Trend analysis failed", detail: errorMessage(error2) });
8975
+ sendJson3(res, 500, { error: "Trend analysis failed", detail: errorMessage(error2) });
8736
8976
  }
8737
8977
  }
8738
8978
  async function handleTechStackRemediationPlan(res, deps2) {
8739
8979
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8740
8980
  if (!snapshot) {
8741
- sendJson2(res, 404, { error: "No TechStack snapshot is available" });
8981
+ sendJson3(res, 404, { error: "No TechStack snapshot is available" });
8742
8982
  return;
8743
8983
  }
8744
8984
  const { applyPlan, generateUpgradePlan } = await import("@wrongstack/techstack");
8745
8985
  const plan = generateUpgradePlan(snapshot);
8746
- sendJson2(res, 200, { plan, preview: await applyPlan(plan) });
8986
+ sendJson3(res, 200, { plan, preview: await applyPlan(plan) });
8747
8987
  }
8748
8988
  async function handleTechStackRemediationApply(req, res, deps2) {
8749
8989
  if (!deps2.executePackageOperation) {
8750
- sendJson2(res, 503, { error: "Permission-governed package execution is unavailable" });
8990
+ sendJson3(res, 503, { error: "Permission-governed package execution is unavailable" });
8751
8991
  return;
8752
8992
  }
8753
8993
  let approvedItems;
@@ -8760,16 +9000,16 @@ async function handleTechStackRemediationApply(req, res, deps2) {
8760
9000
  const body = JSON.parse(raw || "{}");
8761
9001
  approvedItems = Array.isArray(body.approvedItems) ? body.approvedItems.filter((value) => typeof value === "string") : [];
8762
9002
  } catch (error2) {
8763
- sendJson2(res, 400, { error: "Invalid request body", detail: errorMessage(error2) });
9003
+ sendJson3(res, 400, { error: "Invalid request body", detail: errorMessage(error2) });
8764
9004
  return;
8765
9005
  }
8766
9006
  if (approvedItems.length === 0) {
8767
- sendJson2(res, 400, { error: "approvedItems must explicitly identify at least one plan item" });
9007
+ sendJson3(res, 400, { error: "approvedItems must explicitly identify at least one plan item" });
8768
9008
  return;
8769
9009
  }
8770
9010
  const snapshot = deps2.store.getSnapshot(deps2.projectId);
8771
9011
  if (!snapshot) {
8772
- sendJson2(res, 404, { error: "No TechStack snapshot is available" });
9012
+ sendJson3(res, 404, { error: "No TechStack snapshot is available" });
8773
9013
  return;
8774
9014
  }
8775
9015
  const approved = new Set(approvedItems);
@@ -8784,14 +9024,17 @@ async function handleTechStackRemediationApply(req, res, deps2) {
8784
9024
  return executePackageOperation(operation, workspace?.relativeRoot);
8785
9025
  }
8786
9026
  });
8787
- sendJson2(res, 200, { plan, result });
9027
+ sendJson3(res, 200, { plan, result });
8788
9028
  }
8789
9029
 
8790
9030
  // src/server/ws-auth.ts
8791
9031
  import { Buffer as Buffer2 } from "node:buffer";
8792
9032
  import { timingSafeEqual } from "node:crypto";
9033
+ import { isLoopbackHost as isLoopbackHostCore } from "@wrongstack/core/hq";
8793
9034
  function isLoopbackHostname(hostname) {
8794
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
9035
+ const normalized = hostname.toLowerCase();
9036
+ if (normalized === "localhost") return true;
9037
+ return isLoopbackHostCore(normalized);
8795
9038
  }
8796
9039
  function effectivePort(url) {
8797
9040
  if (url.port) return url.port;
@@ -8812,7 +9055,7 @@ function isTrustedLoopbackOrigin(origin, hostHeader) {
8812
9055
  }
8813
9056
  }
8814
9057
  function isLoopbackBind(wsHost) {
8815
- return wsHost === "127.0.0.1" || wsHost === "::1" || wsHost === "localhost";
9058
+ return isLoopbackHostCore(wsHost) || wsHost === "localhost";
8816
9059
  }
8817
9060
  function isWildcardBind(wsHost) {
8818
9061
  return wsHost === "0.0.0.0" || wsHost === "::" || wsHost === "[::]";
@@ -9188,6 +9431,88 @@ function createHttpServer(opts) {
9188
9431
  await handleApiAnalyticsSummary(res);
9189
9432
  return;
9190
9433
  }
9434
+ if (url.pathname === "/api/requirement-intakes" && req.method === "GET") {
9435
+ if (requireAccessToken && !accessTokenOk) {
9436
+ res.writeHead(401, { "Content-Type": "application/json" });
9437
+ res.end(JSON.stringify({ error: "Unauthorized" }));
9438
+ return;
9439
+ }
9440
+ await handleRequirementIntakeListForServer(res, opts.intakeService, opts.projectRoot);
9441
+ return;
9442
+ }
9443
+ const intakeProjectMatch = url.pathname.match(
9444
+ /^\/api\/projects\/([^/]+)\/requirement-intakes$/
9445
+ );
9446
+ if (intakeProjectMatch && req.method === "POST") {
9447
+ if (requireAccessToken && !accessTokenOk) {
9448
+ res.writeHead(401, { "Content-Type": "application/json" });
9449
+ res.end(JSON.stringify({ error: "Unauthorized" }));
9450
+ return;
9451
+ }
9452
+ await handleRequirementIntakeCreate(
9453
+ res,
9454
+ req,
9455
+ opts.intakeService,
9456
+ decodeURIComponent(intakeProjectMatch[1])
9457
+ );
9458
+ return;
9459
+ }
9460
+ if (intakeProjectMatch && req.method === "GET") {
9461
+ if (requireAccessToken && !accessTokenOk) {
9462
+ res.writeHead(401, { "Content-Type": "application/json" });
9463
+ res.end(JSON.stringify({ error: "Unauthorized" }));
9464
+ return;
9465
+ }
9466
+ await handleRequirementIntakeList(
9467
+ res,
9468
+ opts.intakeService,
9469
+ decodeURIComponent(intakeProjectMatch[1])
9470
+ );
9471
+ return;
9472
+ }
9473
+ const intakeIdMatch = url.pathname.match(/^\/api\/requirement-intakes\/([^/]+)$/);
9474
+ if (intakeIdMatch && req.method === "GET") {
9475
+ if (requireAccessToken && !accessTokenOk) {
9476
+ res.writeHead(401, { "Content-Type": "application/json" });
9477
+ res.end(JSON.stringify({ error: "Unauthorized" }));
9478
+ return;
9479
+ }
9480
+ await handleRequirementIntakeGet(res, opts.intakeService, intakeIdMatch[1]);
9481
+ return;
9482
+ }
9483
+ if (intakeIdMatch && req.method === "PATCH") {
9484
+ if (requireAccessToken && !accessTokenOk) {
9485
+ res.writeHead(401, { "Content-Type": "application/json" });
9486
+ res.end(JSON.stringify({ error: "Unauthorized" }));
9487
+ return;
9488
+ }
9489
+ await handleRequirementIntakeUpdate(res, req, opts.intakeService, intakeIdMatch[1]);
9490
+ return;
9491
+ }
9492
+ const intakeActionMatch = url.pathname.match(
9493
+ /^\/api\/requirement-intakes\/([^/]+)\/(answers|suggestions|submit|cancel|archive)$/
9494
+ );
9495
+ if (intakeActionMatch && req.method === "POST") {
9496
+ if (requireAccessToken && !accessTokenOk) {
9497
+ res.writeHead(401, { "Content-Type": "application/json" });
9498
+ res.end(JSON.stringify({ error: "Unauthorized" }));
9499
+ return;
9500
+ }
9501
+ const intakeId = intakeActionMatch[1];
9502
+ const action = intakeActionMatch[2];
9503
+ if (action === "answers") {
9504
+ await handleRequirementIntakeAnswers(res, req, opts.intakeService, intakeId);
9505
+ } else if (action === "suggestions") {
9506
+ await handleRequirementIntakeSuggestions(res, req, opts.intakeService, intakeId);
9507
+ } else if (action === "submit") {
9508
+ await handleRequirementIntakeSubmit(res, opts.intakeService, intakeId);
9509
+ } else if (action === "cancel") {
9510
+ await handleRequirementIntakeCancel(res, req, opts.intakeService, intakeId);
9511
+ } else {
9512
+ await handleRequirementIntakeArchive(res, opts.intakeService, intakeId);
9513
+ }
9514
+ return;
9515
+ }
9191
9516
  if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
9192
9517
  if (requireAccessToken && !accessTokenOk) {
9193
9518
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -9383,17 +9708,25 @@ function createHttpServer(opts) {
9383
9708
  res.end(JSON.stringify({ error: "Project root not configured" }));
9384
9709
  return;
9385
9710
  }
9386
- await handleDeadCodeScan(res, {
9387
- projectRoot: opts.projectRoot,
9388
- indexDir: opts.indexDir
9389
- }, req);
9711
+ await handleDeadCodeScan(
9712
+ res,
9713
+ {
9714
+ projectRoot: opts.projectRoot,
9715
+ indexDir: opts.indexDir
9716
+ },
9717
+ req
9718
+ );
9390
9719
  return;
9391
9720
  }
9392
9721
  if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
9393
- await handleDeadCodeActionPlan(res, {
9394
- projectRoot: opts.projectRoot ?? "",
9395
- indexDir: opts.indexDir
9396
- }, req);
9722
+ await handleDeadCodeActionPlan(
9723
+ res,
9724
+ {
9725
+ projectRoot: opts.projectRoot ?? "",
9726
+ indexDir: opts.indexDir
9727
+ },
9728
+ req
9729
+ );
9397
9730
  return;
9398
9731
  }
9399
9732
  if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
@@ -13000,6 +13333,27 @@ import { spawn as spawn2 } from "node:child_process";
13000
13333
  import { existsSync } from "node:fs";
13001
13334
  import { findPackageJSON } from "node:module";
13002
13335
  import * as path15 from "node:path";
13336
+
13337
+ // src/server/intake-service.ts
13338
+ import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
13339
+ import {
13340
+ AllowAllIntakeAuthorizer,
13341
+ RequirementIntakeService,
13342
+ RequirementIntakeStore
13343
+ } from "@wrongstack/requirement-intake";
13344
+ function createProjectIntakeService(opts) {
13345
+ return new RequirementIntakeService({
13346
+ store: new RequirementIntakeStore({
13347
+ baseDir: resolveWstackPaths4({
13348
+ projectRoot: opts.projectRoot,
13349
+ globalRoot: opts.globalRoot
13350
+ }).projectRequirementIntakes
13351
+ }),
13352
+ authorizer: new AllowAllIntakeAuthorizer()
13353
+ });
13354
+ }
13355
+
13356
+ // src/server/frontend-static-serve.ts
13003
13357
  function resolveDistDir(input) {
13004
13358
  const options = typeof input === "string" ? { explicitDistDir: input } : input ?? {};
13005
13359
  if (options.explicitDistDir) return path15.resolve(options.explicitDistDir);
@@ -13084,6 +13438,10 @@ async function startStaticServe(opts, deps2 = {}) {
13084
13438
  const create = deps2.createServer ?? createHttpServer;
13085
13439
  const distDir = deps2.resolveDist ? deps2.resolveDist(opts.distDir) : await ensureDist(opts.distDir);
13086
13440
  if (distDir === null) return null;
13441
+ const intakeService = opts.intakeService ?? (opts.projectRoot ? createProjectIntakeService({
13442
+ projectRoot: opts.projectRoot,
13443
+ globalRoot: opts.globalRoot
13444
+ }) : void 0);
13087
13445
  const server = create({
13088
13446
  host: opts.host,
13089
13447
  port: opts.httpPort,
@@ -13096,7 +13454,8 @@ async function startStaticServe(opts, deps2 = {}) {
13096
13454
  publicWsUrl: opts.publicWsUrl,
13097
13455
  apiToken: opts.apiToken,
13098
13456
  requireToken: opts.requireToken,
13099
- allowedHostnames: opts.allowedHostnames
13457
+ allowedHostnames: opts.allowedHostnames,
13458
+ intakeService
13100
13459
  });
13101
13460
  if (!opts.deferListen) {
13102
13461
  server.listen(opts.httpPort, opts.host);
@@ -13511,7 +13870,21 @@ function createWebuiClientPresence(deps2) {
13511
13870
  }
13512
13871
 
13513
13872
  // src/server/brain-handlers.ts
13873
+ import { BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
13514
13874
  import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
13875
+ var COUNCIL_PERSONA_CATALOG = Object.freeze(
13876
+ BUILTIN_COUNCIL_PERSONAS.map(
13877
+ (persona) => Object.freeze({
13878
+ id: persona.id,
13879
+ name: persona.name,
13880
+ description: persona.description,
13881
+ ...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {}
13882
+ })
13883
+ )
13884
+ );
13885
+ function brainConfigPayload(runtime) {
13886
+ return { ...runtime.getSnapshot(), personaCatalog: COUNCIL_PERSONA_CATALOG };
13887
+ }
13515
13888
  function sendResult6(ctx, ws, success, message) {
13516
13889
  ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
13517
13890
  }
@@ -13551,7 +13924,7 @@ function handleBrainConfigGet(ctx, ws) {
13551
13924
  }
13552
13925
  ctx.send(ws, {
13553
13926
  type: "brain.config",
13554
- payload: { config: ctx.brainRuntime.getSnapshot(), persisted: true }
13927
+ payload: { config: brainConfigPayload(ctx.brainRuntime), persisted: true }
13555
13928
  });
13556
13929
  }
13557
13930
  async function handleBrainConfigSet(ctx, ws, payload) {
@@ -13575,7 +13948,7 @@ async function handleBrainConfigSet(ctx, ws, payload) {
13575
13948
  ctx.send(ws, {
13576
13949
  type: "brain.config",
13577
13950
  payload: {
13578
- config: ctx.brainRuntime.getSnapshot(),
13951
+ config: brainConfigPayload(ctx.brainRuntime),
13579
13952
  persisted: result.ok,
13580
13953
  ...result.ok ? {} : { error: result.error ?? "Persist failed." }
13581
13954
  }
@@ -13585,7 +13958,7 @@ async function handleBrainConfigSet(ctx, ws, payload) {
13585
13958
  ctx.send(ws, {
13586
13959
  type: "brain.config",
13587
13960
  payload: {
13588
- config: ctx.brainRuntime.getSnapshot(),
13961
+ config: brainConfigPayload(ctx.brainRuntime),
13589
13962
  persisted: false,
13590
13963
  error: `Invalid Brain setting: ${toErrorMessage5(err)}`
13591
13964
  }
@@ -15069,7 +15442,7 @@ import { makeProviderFromConfig } from "@wrongstack/providers";
15069
15442
  import * as fs14 from "node:fs/promises";
15070
15443
  import * as path19 from "node:path";
15071
15444
  import { DefaultSessionStore } from "@wrongstack/core/storage";
15072
- import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
15445
+ import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
15073
15446
  function createProjectHandlers(ctx) {
15074
15447
  const sendTo = (ws, message) => {
15075
15448
  if (ctx.sendMessage) ctx.sendMessage(ws, message);
@@ -15187,7 +15560,7 @@ function createProjectHandlers(ctx) {
15187
15560
  });
15188
15561
  return;
15189
15562
  }
15190
- const paths = resolveWstackPaths4({
15563
+ const paths = resolveWstackPaths5({
15191
15564
  projectRoot: resolved,
15192
15565
  globalRoot: ctx.wpaths.globalRoot
15193
15566
  });
@@ -15198,7 +15571,7 @@ function createProjectHandlers(ctx) {
15198
15571
  const previous = ctx.getSession();
15199
15572
  const previousId = previous.id;
15200
15573
  const previousProjectRoot = ctx.getProjectRoot();
15201
- const previousPaths = resolveWstackPaths4({
15574
+ const previousPaths = resolveWstackPaths5({
15202
15575
  projectRoot: previousProjectRoot,
15203
15576
  globalRoot: ctx.wpaths.globalRoot
15204
15577
  });
@@ -16184,6 +16557,7 @@ var CLIENT_CONVERSATION_MESSAGE_TYPES = [
16184
16557
  "modes.list",
16185
16558
  "session.checkpoints",
16186
16559
  "session.delete",
16560
+ "session.inspect",
16187
16561
  "session.new",
16188
16562
  "session.rename",
16189
16563
  "session.resume",
@@ -16447,6 +16821,7 @@ var SERVER_CONVERSATION_MESSAGE_TYPES = [
16447
16821
  "session.checkpoints",
16448
16822
  "session.damaged",
16449
16823
  "session.end",
16824
+ "session.inspect",
16450
16825
  "session.rewound",
16451
16826
  "session.start",
16452
16827
  "session.stats",
@@ -16981,6 +17356,271 @@ function negotiateProtocol(peer) {
16981
17356
  }
16982
17357
 
16983
17358
  // src/server/session-history.ts
17359
+ function blockText(block) {
17360
+ if (typeof block === "string") return block;
17361
+ switch (block.type) {
17362
+ case "text":
17363
+ return block.text;
17364
+ case "thinking":
17365
+ return block.thinking;
17366
+ case "tool_use":
17367
+ return `[tool:${block.name}]`;
17368
+ case "tool_result":
17369
+ return block.content;
17370
+ case "image":
17371
+ return "[image]";
17372
+ default: {
17373
+ const _exhaustive = block;
17374
+ void _exhaustive;
17375
+ return "[block]";
17376
+ }
17377
+ }
17378
+ }
17379
+ function labelForEvent(e) {
17380
+ switch (e.type) {
17381
+ case "session_start":
17382
+ return "Session started";
17383
+ case "session_resumed":
17384
+ return "Session resumed";
17385
+ case "session_forked":
17386
+ return `Forked from ${e.parentSessionId}`;
17387
+ case "user_input": {
17388
+ const content = typeof e.content === "string" ? e.content : Array.isArray(e.content) ? e.content.map(blockText).join("") : "[input]";
17389
+ return `User: ${content.length > 100 ? content.slice(0, 100) + "\u2026" : content}`;
17390
+ }
17391
+ case "llm_request":
17392
+ return "LLM request";
17393
+ case "llm_response":
17394
+ return "LLM response";
17395
+ case "tool_use":
17396
+ return `Tool: ${e.name}`;
17397
+ case "tool_call_start":
17398
+ return `Tool start: ${e.name}`;
17399
+ case "tool_call_end":
17400
+ return `Tool done: ${e.name} (${e.durationMs}ms, ${e.ok === false ? "error" : "ok"})`;
17401
+ case "tool_result":
17402
+ return `Tool result: ${e.id}${e.isError ? " (error)" : ""}`;
17403
+ case "tool_progress":
17404
+ return `Progress: ${e.name} \u2014 ${e.event.type}`;
17405
+ case "compaction":
17406
+ return `Compaction: ${e.before} \u2192 ${e.after} tokens`;
17407
+ case "context_snapshot":
17408
+ return "Context snapshot";
17409
+ case "error":
17410
+ return `Error: ${e.message.length > 100 ? e.message.slice(0, 100) + "\u2026" : e.message}`;
17411
+ case "session_end":
17412
+ return "Session ended";
17413
+ case "message_appended":
17414
+ return `Message appended: ${e.message.role}`;
17415
+ case "message_updated":
17416
+ return "Message updated";
17417
+ case "messages_replaced": {
17418
+ const count = e.messagesOmitted ?? e.messages.length;
17419
+ return `Messages replaced (${e.messagesOmitted ? "~" : ""}${count} msgs)`;
17420
+ }
17421
+ case "message_truncated":
17422
+ return `Message truncated: ${e.before} \u2192 ${e.after}`;
17423
+ case "file_event":
17424
+ return `File ${e.operation}: ${e.filePath} (${e.toolName})`;
17425
+ case "file_snapshot":
17426
+ return `File snapshot at prompt #${e.promptIndex}`;
17427
+ case "file_observation":
17428
+ return `File observation: ${e.path}`;
17429
+ case "rewound":
17430
+ return `Rewound to prompt #${e.toPromptIndex} (${e.revertedFiles.length} files)`;
17431
+ case "mode_changed":
17432
+ return `Mode: ${e.from} \u2192 ${e.to}`;
17433
+ case "task_created":
17434
+ return `Task created: ${e.title}`;
17435
+ case "task_updated":
17436
+ return `Task ${e.taskId}: ${e.status}`;
17437
+ case "task_completed":
17438
+ return `Task done: ${e.title}`;
17439
+ case "task_failed":
17440
+ return `Task failed: ${e.title} \u2014 ${e.error.length > 80 ? e.error.slice(0, 80) + "\u2026" : e.error}`;
17441
+ case "agent_spawned":
17442
+ return `Agent spawned: ${e.role}`;
17443
+ case "agent_stopped":
17444
+ return "Agent stopped";
17445
+ case "agent_error":
17446
+ return `Agent error: ${e.error.length > 80 ? e.error.slice(0, 80) + "\u2026" : e.error}`;
17447
+ case "provider_retry":
17448
+ return `Retry: ${e.description} (attempt ${e.attempt})`;
17449
+ case "provider_error":
17450
+ return `Provider error: ${e.description}`;
17451
+ case "checkpoint":
17452
+ return `Checkpoint at prompt #${e.promptIndex}`;
17453
+ case "in_flight_start":
17454
+ return `In-flight started: ${e.context}`;
17455
+ case "in_flight_end":
17456
+ return `In-flight ended: ${e.reason}`;
17457
+ case "side_effect":
17458
+ return `Side effect: ${e.toolName} (${e.risk})`;
17459
+ case "spec_parsed":
17460
+ return `Spec parsed: ${e.title}`;
17461
+ case "spec_analyzed":
17462
+ return `Spec analyzed: ${e.specId}`;
17463
+ case "skill_activated":
17464
+ return `Skill: ${e.skillName}`;
17465
+ case "skill_deactivated":
17466
+ return `Skill done: ${e.skillName}`;
17467
+ default: {
17468
+ const _exhaustive = e;
17469
+ void _exhaustive;
17470
+ return String(_exhaustive);
17471
+ }
17472
+ }
17473
+ }
17474
+ function detailForEvent(e) {
17475
+ switch (e.type) {
17476
+ case "session_start":
17477
+ return `${e.model} @ ${e.provider}`;
17478
+ case "session_resumed":
17479
+ return `${e.model} @ ${e.provider}`;
17480
+ case "session_forked":
17481
+ return `parent checkpoint: ${e.parentCheckpointHash.slice(0, 12)}\u2026`;
17482
+ case "llm_request":
17483
+ return `${e.model} \xB7 ${e.messageCount} msgs \xB7 ${e.toolCount ?? "?"} tools`;
17484
+ case "llm_response":
17485
+ return `${e.stopReason} \xB7 ${e.usage.input ?? 0}+${e.usage.output ?? 0} tokens`;
17486
+ case "tool_use":
17487
+ return `id: ${e.id}`;
17488
+ case "tool_call_start":
17489
+ return `id: ${e.id}`;
17490
+ case "tool_call_end":
17491
+ return `${(e.outputBytes ?? e.outputSize ?? 0).toLocaleString()} B \xB7 ${e.outputLines ?? 0} lines`;
17492
+ case "tool_progress":
17493
+ return `${e.event.type}${e.event.text ? `: ${e.event.text}` : ""}`;
17494
+ case "compaction":
17495
+ return `saved ~${Math.max(0, e.before - e.after)} tokens`;
17496
+ case "context_snapshot":
17497
+ return `${e.messages.length} msgs${e.messagesOmitted ? ` (${e.messagesOmitted} omitted)` : ""}`;
17498
+ case "error":
17499
+ return `phase: ${e.phase}`;
17500
+ case "session_end":
17501
+ return `${e.usage.input ?? 0}+${e.usage.output ?? 0} total tokens`;
17502
+ case "message_appended":
17503
+ return `appended ${e.message.role}`;
17504
+ case "message_updated":
17505
+ return `at index ${e.index}`;
17506
+ case "messages_replaced":
17507
+ return `${e.messagesOmitted ?? e.messages.length} total`;
17508
+ case "message_truncated":
17509
+ return `truncated to ${e.after} tokens`;
17510
+ case "mode_changed":
17511
+ return `${e.from} \u2192 ${e.to}`;
17512
+ case "task_created":
17513
+ return `id: ${e.taskId}`;
17514
+ case "task_updated":
17515
+ return `${e.taskId}: ${e.status}`;
17516
+ case "task_completed":
17517
+ return `${e.taskId}`;
17518
+ case "task_failed":
17519
+ return `${e.taskId}: ${e.error}`;
17520
+ case "agent_spawned":
17521
+ return `id: ${e.agentId} (${e.role})`;
17522
+ case "agent_stopped":
17523
+ return `id: ${e.agentId}`;
17524
+ case "agent_error":
17525
+ return `id: ${e.agentId}: ${e.error}`;
17526
+ case "file_event":
17527
+ return `${e.filePath} (${e.toolName})`;
17528
+ case "file_snapshot":
17529
+ return `${e.files.length} files at prompt #${e.promptIndex}`;
17530
+ case "file_observation":
17531
+ return `${e.source === "user" ? "user-saved" : "tool-written"} at ${e.path}`;
17532
+ case "rewound":
17533
+ return `${e.revertedFiles.length} files reverted`;
17534
+ case "in_flight_start":
17535
+ return e.context;
17536
+ case "in_flight_end":
17537
+ return `${e.reason}`;
17538
+ case "side_effect":
17539
+ return `${e.toolName} (${e.risk})${e.outcome ? `: ${e.outcome}` : ""}`;
17540
+ case "provider_retry":
17541
+ return `delay ${e.delayMs}ms${e.status ? ` \xB7 HTTP ${e.status}` : ""}`;
17542
+ case "provider_error":
17543
+ return `${e.retryable ? "retryable" : "fatal"}${e.status ? ` \xB7 HTTP ${e.status}` : ""}`;
17544
+ case "user_input":
17545
+ return typeof e.content === "string" ? `${e.content.length} chars` : `${e.content.length} blocks`;
17546
+ case "tool_result":
17547
+ return `${e.isError ? "error" : "ok"}`;
17548
+ case "checkpoint":
17549
+ return `prompt #${e.promptIndex}`;
17550
+ case "spec_parsed":
17551
+ return `${e.title} (${e.completeness}% complete)`;
17552
+ case "spec_analyzed":
17553
+ return `${e.gaps.length} gaps identified`;
17554
+ case "skill_activated":
17555
+ return `at ${e.skillName}`;
17556
+ case "skill_deactivated":
17557
+ return `at ${e.skillName}`;
17558
+ default: {
17559
+ const _exhaustive = e;
17560
+ void _exhaustive;
17561
+ return "";
17562
+ }
17563
+ }
17564
+ }
17565
+ function buildInspectPayload(summary, events, fallback) {
17566
+ const inspectEvents = events.map((e) => ({
17567
+ ts: e.ts,
17568
+ type: e.type,
17569
+ label: labelForEvent(e),
17570
+ detail: detailForEvent(e)
17571
+ }));
17572
+ const fileEvents = [];
17573
+ let computedToolCallCount = 0;
17574
+ let computedToolErrorCount = 0;
17575
+ let computedFileChangeCount = 0;
17576
+ let computedCompactionCount = 0;
17577
+ let computedMessageCount = 0;
17578
+ let computedIterationCount = 0;
17579
+ const computedToolBreakdown = {};
17580
+ for (const e of events) {
17581
+ if (e.type === "file_event") {
17582
+ fileEvents.push({
17583
+ operation: e.operation,
17584
+ filePath: e.filePath,
17585
+ toolName: e.toolName,
17586
+ ts: e.ts
17587
+ });
17588
+ computedFileChangeCount++;
17589
+ } else if (e.type === "tool_call_end") {
17590
+ computedToolCallCount++;
17591
+ if (e.ok === false) computedToolErrorCount++;
17592
+ computedToolBreakdown[e.name] = (computedToolBreakdown[e.name] ?? 0) + 1;
17593
+ } else if (e.type === "compaction") {
17594
+ computedCompactionCount++;
17595
+ } else if (e.type === "user_input") {
17596
+ computedMessageCount++;
17597
+ } else if (e.type === "llm_response") {
17598
+ computedIterationCount++;
17599
+ }
17600
+ }
17601
+ const s = summary;
17602
+ return {
17603
+ id: s?.id ?? fallback.id,
17604
+ title: s?.title ?? fallback.title,
17605
+ ...s?.name !== void 0 ? { name: s.name } : {},
17606
+ model: s?.model ?? fallback.model,
17607
+ provider: s?.provider ?? fallback.provider,
17608
+ startedAt: s?.startedAt ?? fallback.startedAt,
17609
+ ...(s?.endedAt ?? fallback.endedAt) !== void 0 ? { endedAt: s?.endedAt ?? fallback.endedAt } : {},
17610
+ tokenTotal: s?.tokenTotal ?? 0,
17611
+ ...s?.outcome !== void 0 ? { outcome: s.outcome } : {},
17612
+ messageCount: s?.messageCount ?? computedMessageCount,
17613
+ iterationCount: s?.iterationCount ?? computedIterationCount,
17614
+ toolCallCount: s?.toolCallCount ?? computedToolCallCount,
17615
+ toolErrorCount: s?.toolErrorCount ?? computedToolErrorCount,
17616
+ fileChangeCount: s?.fileChangeCount ?? computedFileChangeCount,
17617
+ compactionCount: s?.compactionCount ?? computedCompactionCount,
17618
+ toolBreakdown: s?.toolBreakdown ?? computedToolBreakdown,
17619
+ events: inspectEvents,
17620
+ fileEvents,
17621
+ ...s?.lastUserMessage !== void 0 ? { lastUserMessage: s.lastUserMessage } : {}
17622
+ };
17623
+ }
16984
17624
  function toSessionHistoryEntry(summary, currentSessionId2) {
16985
17625
  return {
16986
17626
  id: summary.id,
@@ -17527,6 +18167,47 @@ function createSessionHandlers(ctx) {
17527
18167
  if (!ensureCurrentSession(ws, msg, "session.save")) return;
17528
18168
  result(ws, true, `Session ${ctx.getSession().id} is auto-saved`);
17529
18169
  },
18170
+ inspectSession: async (ws, msg) => {
18171
+ const { id } = msg.payload;
18172
+ if (!id) {
18173
+ sendTo(ws, {
18174
+ type: "session.inspect",
18175
+ payload: { id: "", error: "Session id is required" }
18176
+ });
18177
+ return;
18178
+ }
18179
+ try {
18180
+ const store = ctx.getSessionStore();
18181
+ const data = await store.load(id);
18182
+ let summary;
18183
+ try {
18184
+ const summaries = await store.list(200);
18185
+ summary = summaries.find((s) => s.id === id);
18186
+ } catch {
18187
+ summary = void 0;
18188
+ }
18189
+ const payload = buildInspectPayload(summary, data.events, {
18190
+ id: data.metadata.id,
18191
+ title: data.metadata.title ?? "",
18192
+ model: data.metadata.model ?? "",
18193
+ provider: data.metadata.provider ?? "",
18194
+ startedAt: data.metadata.startedAt,
18195
+ endedAt: data.metadata.endedAt
18196
+ });
18197
+ sendTo(ws, {
18198
+ type: "session.inspect",
18199
+ payload
18200
+ });
18201
+ } catch (err) {
18202
+ sendTo(ws, {
18203
+ type: "session.inspect",
18204
+ payload: {
18205
+ id,
18206
+ error: err instanceof Error ? err.message : String(err)
18207
+ }
18208
+ });
18209
+ }
18210
+ },
17530
18211
  listCheckpoints: async (ws, msg) => {
17531
18212
  if (!ensureCurrentSession(ws, msg, "session.checkpoints")) return;
17532
18213
  try {
@@ -18448,7 +19129,7 @@ async function handleProviderRoute(ws, msg, routes) {
18448
19129
  const customModelRaw = payload["customModel"];
18449
19130
  if (!isRecord4(customModelRaw)) return invalidPayload(ws, msg.type);
18450
19131
  const cm = optionalCustomModels({ customModels: { [modelId]: customModelRaw } });
18451
- if (!cm || !cm[modelId]) return invalidPayload(ws, msg.type);
19132
+ if (!cm?.[modelId]) return invalidPayload(ws, msg.type);
18452
19133
  await routes.providerHandlers.handleCustomModelSet(ws, providerId, modelId, cm[modelId]);
18453
19134
  return true;
18454
19135
  }
@@ -18642,6 +19323,9 @@ async function handleSessionRoute(ws, msg, handlers) {
18642
19323
  case "session.save":
18643
19324
  await handlers.saveSession(ws, msg);
18644
19325
  return true;
19326
+ case "session.inspect":
19327
+ await handlers.inspectSession(ws, msg);
19328
+ return true;
18645
19329
  case "session.checkpoints":
18646
19330
  await handlers.listCheckpoints(ws, msg);
18647
19331
  return true;
@@ -20974,7 +21658,8 @@ function setupEvents(deps2) {
20974
21658
  attempt: e.attempt,
20975
21659
  delayMs: e.delayMs,
20976
21660
  status: e.status,
20977
- description: e.description
21661
+ description: e.description,
21662
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
20978
21663
  })
20979
21664
  });
20980
21665
  appendForCurrentSession(e.sessionId, {
@@ -20984,7 +21669,8 @@ function setupEvents(deps2) {
20984
21669
  attempt: e.attempt,
20985
21670
  delayMs: e.delayMs,
20986
21671
  status: e.status,
20987
- description: e.description
21672
+ description: e.description,
21673
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
20988
21674
  });
20989
21675
  });
20990
21676
  on("provider.status_changed", (e) => {
@@ -21024,7 +21710,8 @@ function setupEvents(deps2) {
21024
21710
  providerId: e.providerId,
21025
21711
  status: e.status,
21026
21712
  description: e.description,
21027
- retryable: e.retryable
21713
+ retryable: e.retryable,
21714
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
21028
21715
  })
21029
21716
  });
21030
21717
  appendForCurrentSession(e.sessionId, {
@@ -21033,7 +21720,8 @@ function setupEvents(deps2) {
21033
21720
  providerId: e.providerId,
21034
21721
  status: e.status,
21035
21722
  description: e.description,
21036
- retryable: e.retryable
21723
+ retryable: e.retryable,
21724
+ ...e.errorBody ? { errorBody: e.errorBody } : {}
21037
21725
  });
21038
21726
  });
21039
21727
  on("provider.fallback", (e) => {
@@ -24110,7 +24798,7 @@ import { makeProviderFromConfig as makeProviderFromConfig4, withCatalogCapabilit
24110
24798
 
24111
24799
  // src/server/mode-handlers.ts
24112
24800
  import { DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2 } from "@wrongstack/core/agent";
24113
- import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
24801
+ import { resolveWstackPaths as resolveWstackPaths6 } from "@wrongstack/core/utils";
24114
24802
  function createModeHandlers(context) {
24115
24803
  return createModeRouteHandlers({
24116
24804
  modeStore: context.modeStore,
@@ -24119,7 +24807,7 @@ function createModeHandlers(context) {
24119
24807
  send,
24120
24808
  afterSwitch: async (id) => {
24121
24809
  const modePrompt = id === "default" ? "" : (await context.modeStore.getMode(id))?.prompt ?? "";
24122
- const paths = resolveWstackPaths5({
24810
+ const paths = resolveWstackPaths6({
24123
24811
  projectRoot: context.projectRoot,
24124
24812
  globalRoot: context.globalRoot
24125
24813
  });
@@ -24479,7 +25167,10 @@ async function resolvePorts(opts) {
24479
25167
  const surface = opts.surface ?? "webui";
24480
25168
  const surfaceDefaults = surface === "simpleui" ? { http: 3466 } : { http: 3456 };
24481
25169
  const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
24482
- const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
25170
+ const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(
25171
+ process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http),
25172
+ 10
25173
+ );
24483
25174
  const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
24484
25175
  const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
24485
25176
  const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
@@ -24488,7 +25179,16 @@ async function resolvePorts(opts) {
24488
25179
  if (!strictPort) {
24489
25180
  httpPort = await findFreePort(wsHost, requestedHttpPort);
24490
25181
  if (httpPort !== requestedHttpPort) {
24491
- console.warn(JSON.stringify({ level: "warn", event: "webui.port_reassigned", protocol: "HTTP", requested: requestedHttpPort, assigned: httpPort, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
25182
+ console.warn(
25183
+ JSON.stringify({
25184
+ level: "warn",
25185
+ event: "webui.port_reassigned",
25186
+ protocol: "HTTP",
25187
+ requested: requestedHttpPort,
25188
+ assigned: httpPort,
25189
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
25190
+ })
25191
+ );
24492
25192
  }
24493
25193
  }
24494
25194
  return { wsHost, httpPort, publicUrl, publicWsUrl, requireToken };
@@ -24594,21 +25294,49 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
24594
25294
  if (eventsArmed) return;
24595
25295
  eventsArmed = true;
24596
25296
  console.log(`[WebUI] Backend ready (${label})`);
24597
- disposeEvents = setupEvents({ ...setupInput, watcherMetrics, onFleetBroadcaster: (fn) => {
24598
- fleetBroadcast = fn;
24599
- } });
25297
+ disposeEvents = setupEvents({
25298
+ ...setupInput,
25299
+ watcherMetrics,
25300
+ onFleetBroadcaster: (fn) => {
25301
+ fleetBroadcast = fn;
25302
+ }
25303
+ });
24600
25304
  };
24601
25305
  wssPrimary.on("listening", () => arm(`${wsHost}:${httpPort}`));
24602
25306
  wssPrimary.on("error", (err) => {
24603
- console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage13(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
25307
+ console.error(
25308
+ JSON.stringify({
25309
+ level: "error",
25310
+ event: "webui.ws_server_error",
25311
+ host: wsHost,
25312
+ message: toErrorMessage13(err),
25313
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
25314
+ })
25315
+ );
24604
25316
  });
24605
25317
  if (wssSecondary) {
24606
25318
  wssSecondary.on("listening", () => arm(`::1:${httpPort}`));
24607
25319
  wssSecondary.on("error", (err) => {
24608
25320
  if (err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL") {
24609
- console.warn(JSON.stringify({ level: "warn", event: "webui.ipv6_unavailable", code: err.code, message: err.message, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
25321
+ console.warn(
25322
+ JSON.stringify({
25323
+ level: "warn",
25324
+ event: "webui.ipv6_unavailable",
25325
+ code: err.code,
25326
+ message: err.message,
25327
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
25328
+ })
25329
+ );
24610
25330
  } else {
24611
- console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: "::1", message: err.message, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
25331
+ console.error(
25332
+ JSON.stringify({
25333
+ level: "error",
25334
+ event: "webui.ws_server_error",
25335
+ host: "::1",
25336
+ message: err.message,
25337
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
25338
+ })
25339
+ );
24612
25340
  }
24613
25341
  });
24614
25342
  }
@@ -24629,6 +25357,7 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
24629
25357
  }
24630
25358
  }
24631
25359
  function startHttpServer(opts) {
25360
+ const intakeService = opts.intakeService ?? createProjectIntakeService({ projectRoot: opts.projectRoot, globalRoot: opts.globalRoot });
24632
25361
  const httpServer = createHttpServer({
24633
25362
  host: opts.wsHost,
24634
25363
  port: opts.httpPort,
@@ -24642,7 +25371,8 @@ function startHttpServer(opts) {
24642
25371
  onTechStackEvent: opts.onTechStackEvent,
24643
25372
  getLlm: opts.getLlm,
24644
25373
  executePackageOperation: opts.executePackageOperation,
24645
- projectRoot: opts.projectRoot
25374
+ projectRoot: opts.projectRoot,
25375
+ intakeService
24646
25376
  });
24647
25377
  return httpServer;
24648
25378
  }