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