@wrongstack/webui-server 0.298.2 → 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 +798 -68
- package/dist/protocol/client-conversation.d.ts +1 -1
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-conversation.d.ts +1 -1
- package/dist/server/entry.js +790 -67
- package/dist/server/frontend-static-serve.d.ts +9 -0
- package/dist/server/http-server.d.ts +7 -0
- package/dist/server/intake-service.d.ts +13 -0
- package/dist/server/requirement-intake-handlers.d.ts +41 -0
- package/dist/server/server-runtime.d.ts +3 -0
- package/dist/server/session-history.d.ts +42 -1
- package/dist/server/session-routes.d.ts +1 -0
- package/package.json +11 -10
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
8833
|
+
sendJson3(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
|
|
8594
8834
|
} catch (error2) {
|
|
8595
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8916
|
+
sendJson3(res, 503, { error: "Research unavailable", detail: errorMessage(error2) });
|
|
8677
8917
|
return;
|
|
8678
8918
|
}
|
|
8679
8919
|
if (!researcher) {
|
|
8680
|
-
|
|
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
|
-
|
|
8937
|
+
sendJson3(res, 200, { dependencyId, findings });
|
|
8698
8938
|
} catch (error2) {
|
|
8699
|
-
|
|
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
|
-
|
|
8948
|
+
sendJson3(res, 404, { error: "Job not found" });
|
|
8709
8949
|
return;
|
|
8710
8950
|
}
|
|
8711
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8973
|
+
sendJson3(res, 200, { trend: new TrendStore(deps2.store).analyze(deps2.projectId) });
|
|
8734
8974
|
} catch (error2) {
|
|
8735
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9003
|
+
sendJson3(res, 400, { error: "Invalid request body", detail: errorMessage(error2) });
|
|
8764
9004
|
return;
|
|
8765
9005
|
}
|
|
8766
9006
|
if (approvedItems.length === 0) {
|
|
8767
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
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(
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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({
|
|
24598
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|