@wrongstack/webui-server 0.296.2 → 0.296.3
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 +837 -238
- package/dist/index.js.map +4 -4
- package/dist/protocol/client-workspace.d.ts +1 -1
- package/dist/protocol/client-workspace.d.ts.map +1 -1
- package/dist/protocol/index.js +4 -1
- package/dist/protocol/index.js.map +2 -2
- package/dist/protocol/projections.d.ts +6 -0
- package/dist/protocol/projections.d.ts.map +1 -1
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/registry.d.ts.map +1 -1
- package/dist/protocol/server-workspace.d.ts +1 -1
- package/dist/protocol/server-workspace.d.ts.map +1 -1
- package/dist/server/backend-services.d.ts.map +1 -1
- package/dist/server/connections-health-route.d.ts +8 -0
- package/dist/server/connections-health-route.d.ts.map +1 -1
- package/dist/server/embedded-host-adapters.d.ts +3 -0
- package/dist/server/embedded-host-adapters.d.ts.map +1 -1
- package/dist/server/embedded-message-router.d.ts.map +1 -1
- package/dist/server/entry.js +559 -209
- package/dist/server/entry.js.map +4 -4
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/project-handlers.d.ts +1 -0
- package/dist/server/project-handlers.d.ts.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/sdd-board-ws-handler.d.ts +8 -1
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -1
- package/dist/server/session-handlers.d.ts +3 -0
- package/dist/server/session-handlers.d.ts.map +1 -1
- package/dist/server/setup-events.d.ts.map +1 -1
- package/dist/server/start-webui.d.ts +12 -0
- package/dist/server/start-webui.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/server/entry.js
CHANGED
|
@@ -135,85 +135,85 @@ var ENUM_PREF_KEYS = {
|
|
|
135
135
|
autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
|
|
136
136
|
fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
|
|
137
137
|
};
|
|
138
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
138
|
+
function validateModelRuntimeValue(modelRuntime, path29) {
|
|
139
139
|
const reasoning = modelRuntime["reasoning"];
|
|
140
140
|
if (reasoning !== void 0) {
|
|
141
|
-
if (!isRecord(reasoning)) return `${
|
|
141
|
+
if (!isRecord(reasoning)) return `${path29}.reasoning must be an object when provided`;
|
|
142
142
|
const mode = reasoning["mode"];
|
|
143
143
|
const effort = reasoning["effort"];
|
|
144
144
|
const preserve = reasoning["preserve"];
|
|
145
145
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
146
|
-
return `${
|
|
146
|
+
return `${path29}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
147
147
|
}
|
|
148
148
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
149
|
-
return `${
|
|
149
|
+
return `${path29}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
150
150
|
}
|
|
151
151
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
152
|
-
return `${
|
|
152
|
+
return `${path29}.reasoning.preserve must be a boolean when provided`;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
const cache2 = modelRuntime["cache"];
|
|
156
156
|
if (cache2 !== void 0) {
|
|
157
|
-
if (!isRecord(cache2)) return `${
|
|
157
|
+
if (!isRecord(cache2)) return `${path29}.cache must be an object when provided`;
|
|
158
158
|
const ttl = cache2["ttl"];
|
|
159
159
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
160
|
-
return `${
|
|
160
|
+
return `${path29}.cache.ttl must be one of: 5m, 1h`;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
const parameters = modelRuntime["parameters"];
|
|
164
164
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
165
|
-
return `${
|
|
165
|
+
return `${path29}.parameters must be an object when provided`;
|
|
166
166
|
}
|
|
167
167
|
return null;
|
|
168
168
|
}
|
|
169
|
-
function validateModelBlackoutRule(rule,
|
|
169
|
+
function validateModelBlackoutRule(rule, path29) {
|
|
170
170
|
const id = rule["id"];
|
|
171
171
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
172
|
-
return `${
|
|
172
|
+
return `${path29}.id must be a non-empty string`;
|
|
173
173
|
}
|
|
174
174
|
const start = rule["start"];
|
|
175
175
|
if (typeof start !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(start)) {
|
|
176
|
-
return `${
|
|
176
|
+
return `${path29}.start must be a string in HH:mm (00:00-23:59) format`;
|
|
177
177
|
}
|
|
178
178
|
const end = rule["end"];
|
|
179
179
|
if (typeof end !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(end)) {
|
|
180
|
-
return `${
|
|
180
|
+
return `${path29}.end must be a string in HH:mm (00:00-23:59) format`;
|
|
181
181
|
}
|
|
182
182
|
if (rule["enabled"] !== void 0 && typeof rule["enabled"] !== "boolean") {
|
|
183
|
-
return `${
|
|
183
|
+
return `${path29}.enabled must be a boolean when provided`;
|
|
184
184
|
}
|
|
185
185
|
if (rule["provider"] !== void 0 && typeof rule["provider"] !== "string") {
|
|
186
|
-
return `${
|
|
186
|
+
return `${path29}.provider must be a string when provided`;
|
|
187
187
|
}
|
|
188
188
|
if (rule["model"] !== void 0 && typeof rule["model"] !== "string") {
|
|
189
|
-
return `${
|
|
189
|
+
return `${path29}.model must be a string when provided`;
|
|
190
190
|
}
|
|
191
191
|
if (rule["days"] !== void 0) {
|
|
192
|
-
if (!Array.isArray(rule["days"])) return `${
|
|
192
|
+
if (!Array.isArray(rule["days"])) return `${path29}.days must be an array when provided`;
|
|
193
193
|
const seen = /* @__PURE__ */ new Set();
|
|
194
194
|
for (const d of rule["days"]) {
|
|
195
195
|
if (typeof d !== "number" || !Number.isInteger(d) || d < 0 || d > 6) {
|
|
196
|
-
return `${
|
|
196
|
+
return `${path29}.days elements must be integers 0-6 when provided`;
|
|
197
197
|
}
|
|
198
|
-
if (seen.has(d)) return `${
|
|
198
|
+
if (seen.has(d)) return `${path29}.days contains duplicate day: ${d}`;
|
|
199
199
|
seen.add(d);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
if (rule["timezone"] !== void 0) {
|
|
203
203
|
if (typeof rule["timezone"] !== "string") {
|
|
204
|
-
return `${
|
|
204
|
+
return `${path29}.timezone must be a string when provided`;
|
|
205
205
|
}
|
|
206
206
|
try {
|
|
207
207
|
Intl.DateTimeFormat(void 0, { timeZone: rule["timezone"] });
|
|
208
208
|
} catch {
|
|
209
|
-
return `${
|
|
209
|
+
return `${path29}.timezone is not a valid IANA timezone (e.g. "America/New_York")`;
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
if (rule["label"] !== void 0 && typeof rule["label"] !== "string") {
|
|
213
|
-
return `${
|
|
213
|
+
return `${path29}.label must be a string when provided`;
|
|
214
214
|
}
|
|
215
215
|
if (rule["mode"] !== void 0 && rule["mode"] !== "blackout" && rule["mode"] !== "allow_only") {
|
|
216
|
-
return `${
|
|
216
|
+
return `${path29}.mode must be 'blackout' or 'allow_only' when provided`;
|
|
217
217
|
}
|
|
218
218
|
return null;
|
|
219
219
|
}
|
|
@@ -737,8 +737,8 @@ function validateShellOpenPayload(payload) {
|
|
|
737
737
|
if (!isRecord2(payload)) {
|
|
738
738
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
739
739
|
}
|
|
740
|
-
const
|
|
741
|
-
if (typeof
|
|
740
|
+
const path29 = payload["path"];
|
|
741
|
+
if (typeof path29 !== "string" || path29.trim().length === 0) {
|
|
742
742
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
743
743
|
}
|
|
744
744
|
const target = payload["target"];
|
|
@@ -751,7 +751,7 @@ function validateShellOpenPayload(payload) {
|
|
|
751
751
|
return {
|
|
752
752
|
ok: true,
|
|
753
753
|
value: {
|
|
754
|
-
path:
|
|
754
|
+
path: path29,
|
|
755
755
|
...target !== void 0 ? { target } : {}
|
|
756
756
|
}
|
|
757
757
|
};
|
|
@@ -760,14 +760,14 @@ function validateGitDiffPayload(payload) {
|
|
|
760
760
|
if (!isRecord2(payload)) {
|
|
761
761
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
762
762
|
}
|
|
763
|
-
const
|
|
764
|
-
if (
|
|
763
|
+
const path29 = payload["path"];
|
|
764
|
+
if (path29 === void 0 || path29 === null) {
|
|
765
765
|
return { ok: true, value: { path: "" } };
|
|
766
766
|
}
|
|
767
|
-
if (typeof
|
|
767
|
+
if (typeof path29 !== "string") {
|
|
768
768
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
769
769
|
}
|
|
770
|
-
return { ok: true, value: { path:
|
|
770
|
+
return { ok: true, value: { path: path29 } };
|
|
771
771
|
}
|
|
772
772
|
function validateProjectsAddPayload(payload) {
|
|
773
773
|
if (!isRecord2(payload)) {
|
|
@@ -4288,8 +4288,8 @@ function jsonByteLength(value) {
|
|
|
4288
4288
|
return MAX_PAYLOAD_BYTES + 1;
|
|
4289
4289
|
}
|
|
4290
4290
|
}
|
|
4291
|
-
function error(errors,
|
|
4292
|
-
errors.push({ path:
|
|
4291
|
+
function error(errors, path29, code, message) {
|
|
4292
|
+
errors.push({ path: path29, code, message });
|
|
4293
4293
|
}
|
|
4294
4294
|
function isMessageRole(value) {
|
|
4295
4295
|
return value === "user" || value === "assistant" || value === "system";
|
|
@@ -4297,25 +4297,25 @@ function isMessageRole(value) {
|
|
|
4297
4297
|
function isPlainJsonObject(value) {
|
|
4298
4298
|
return isRecord3(value);
|
|
4299
4299
|
}
|
|
4300
|
-
function validateCacheControl(value,
|
|
4300
|
+
function validateCacheControl(value, path29, errors) {
|
|
4301
4301
|
if (value === void 0) return void 0;
|
|
4302
4302
|
if (!isRecord3(value) || value["type"] !== "ephemeral") {
|
|
4303
|
-
error(errors,
|
|
4303
|
+
error(errors, path29, "INVALID_CACHE_CONTROL", 'cache_control must be { type: "ephemeral" }.');
|
|
4304
4304
|
return void 0;
|
|
4305
4305
|
}
|
|
4306
4306
|
return { type: "ephemeral" };
|
|
4307
4307
|
}
|
|
4308
|
-
function validateProviderMeta(value,
|
|
4308
|
+
function validateProviderMeta(value, path29, errors) {
|
|
4309
4309
|
if (value === void 0) return void 0;
|
|
4310
4310
|
if (!isPlainJsonObject(value)) {
|
|
4311
|
-
error(errors,
|
|
4311
|
+
error(errors, path29, "INVALID_PROVIDER_META", "providerMeta must be a JSON object.");
|
|
4312
4312
|
return void 0;
|
|
4313
4313
|
}
|
|
4314
4314
|
return value;
|
|
4315
4315
|
}
|
|
4316
|
-
function validateBlock(value,
|
|
4316
|
+
function validateBlock(value, path29, errors) {
|
|
4317
4317
|
if (!isRecord3(value)) {
|
|
4318
|
-
error(errors,
|
|
4318
|
+
error(errors, path29, "INVALID_BLOCK", "Content block must be an object.");
|
|
4319
4319
|
return void 0;
|
|
4320
4320
|
}
|
|
4321
4321
|
const type = value["type"];
|
|
@@ -4323,14 +4323,14 @@ function validateBlock(value, path28, errors) {
|
|
|
4323
4323
|
case "text": {
|
|
4324
4324
|
const text = value["text"];
|
|
4325
4325
|
if (typeof text !== "string") {
|
|
4326
|
-
error(errors, `${
|
|
4326
|
+
error(errors, `${path29}/text`, "INVALID_TEXT", "Text block text must be a string.");
|
|
4327
4327
|
return void 0;
|
|
4328
4328
|
}
|
|
4329
4329
|
if (text.length > MAX_STRING_LENGTH) {
|
|
4330
|
-
error(errors, `${
|
|
4330
|
+
error(errors, `${path29}/text`, "TEXT_TOO_LARGE", "Text block is too large.");
|
|
4331
4331
|
return void 0;
|
|
4332
4332
|
}
|
|
4333
|
-
const cacheControl = validateCacheControl(value["cache_control"], `${
|
|
4333
|
+
const cacheControl = validateCacheControl(value["cache_control"], `${path29}/cache_control`, errors);
|
|
4334
4334
|
return cacheControl ? { type: "text", text, cache_control: cacheControl } : { type: "text", text };
|
|
4335
4335
|
}
|
|
4336
4336
|
case "tool_use": {
|
|
@@ -4338,15 +4338,15 @@ function validateBlock(value, path28, errors) {
|
|
|
4338
4338
|
const name2 = value["name"];
|
|
4339
4339
|
const input = value["input"];
|
|
4340
4340
|
if (typeof id !== "string" || id.length === 0) {
|
|
4341
|
-
error(errors, `${
|
|
4341
|
+
error(errors, `${path29}/id`, "INVALID_TOOL_USE_ID", "tool_use.id must be a non-empty string.");
|
|
4342
4342
|
}
|
|
4343
4343
|
if (typeof name2 !== "string" || name2.length === 0) {
|
|
4344
|
-
error(errors, `${
|
|
4344
|
+
error(errors, `${path29}/name`, "INVALID_TOOL_NAME", "tool_use.name must be a non-empty string.");
|
|
4345
4345
|
}
|
|
4346
4346
|
if (!isPlainJsonObject(input)) {
|
|
4347
|
-
error(errors, `${
|
|
4347
|
+
error(errors, `${path29}/input`, "INVALID_TOOL_INPUT", "tool_use.input must be an object.");
|
|
4348
4348
|
}
|
|
4349
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4349
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path29}/providerMeta`, errors);
|
|
4350
4350
|
if (typeof id !== "string" || id.length === 0 || typeof name2 !== "string" || name2.length === 0 || !isPlainJsonObject(input)) {
|
|
4351
4351
|
return void 0;
|
|
4352
4352
|
}
|
|
@@ -4358,18 +4358,18 @@ function validateBlock(value, path28, errors) {
|
|
|
4358
4358
|
const content = value["content"];
|
|
4359
4359
|
const isError = value["is_error"];
|
|
4360
4360
|
if (typeof toolUseId !== "string" || toolUseId.length === 0) {
|
|
4361
|
-
error(errors, `${
|
|
4361
|
+
error(errors, `${path29}/tool_use_id`, "INVALID_TOOL_RESULT_ID", "tool_result.tool_use_id must be a non-empty string.");
|
|
4362
4362
|
}
|
|
4363
4363
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
4364
|
-
error(errors, `${
|
|
4364
|
+
error(errors, `${path29}/name`, "INVALID_TOOL_RESULT_NAME", "tool_result.name must be a string.");
|
|
4365
4365
|
}
|
|
4366
4366
|
if (typeof content !== "string") {
|
|
4367
|
-
error(errors, `${
|
|
4367
|
+
error(errors, `${path29}/content`, "INVALID_TOOL_RESULT_CONTENT", "tool_result.content must be a string.");
|
|
4368
4368
|
} else if (content.length > MAX_STRING_LENGTH) {
|
|
4369
|
-
error(errors, `${
|
|
4369
|
+
error(errors, `${path29}/content`, "TOOL_RESULT_TOO_LARGE", "tool_result.content is too large.");
|
|
4370
4370
|
}
|
|
4371
4371
|
if (isError !== void 0 && typeof isError !== "boolean") {
|
|
4372
|
-
error(errors, `${
|
|
4372
|
+
error(errors, `${path29}/is_error`, "INVALID_TOOL_RESULT_ERROR", "tool_result.is_error must be boolean.");
|
|
4373
4373
|
}
|
|
4374
4374
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || typeof content !== "string") return void 0;
|
|
4375
4375
|
return {
|
|
@@ -4383,25 +4383,25 @@ function validateBlock(value, path28, errors) {
|
|
|
4383
4383
|
case "image": {
|
|
4384
4384
|
const source = value["source"];
|
|
4385
4385
|
if (!isRecord3(source)) {
|
|
4386
|
-
error(errors, `${
|
|
4386
|
+
error(errors, `${path29}/source`, "INVALID_IMAGE_SOURCE", "image.source must be an object.");
|
|
4387
4387
|
return void 0;
|
|
4388
4388
|
}
|
|
4389
4389
|
const sourceType = source["type"];
|
|
4390
4390
|
if (sourceType !== "base64" && sourceType !== "url") {
|
|
4391
|
-
error(errors, `${
|
|
4391
|
+
error(errors, `${path29}/source/type`, "INVALID_IMAGE_SOURCE_TYPE", "image.source.type must be base64 or url.");
|
|
4392
4392
|
return void 0;
|
|
4393
4393
|
}
|
|
4394
4394
|
const mediaType = source["media_type"];
|
|
4395
4395
|
const data = source["data"];
|
|
4396
4396
|
const url = source["url"];
|
|
4397
4397
|
if (mediaType !== void 0 && typeof mediaType !== "string") {
|
|
4398
|
-
error(errors, `${
|
|
4398
|
+
error(errors, `${path29}/source/media_type`, "INVALID_IMAGE_MEDIA_TYPE", "image.source.media_type must be a string.");
|
|
4399
4399
|
}
|
|
4400
4400
|
if (data !== void 0 && typeof data !== "string") {
|
|
4401
|
-
error(errors, `${
|
|
4401
|
+
error(errors, `${path29}/source/data`, "INVALID_IMAGE_DATA", "image.source.data must be a string.");
|
|
4402
4402
|
}
|
|
4403
4403
|
if (url !== void 0 && typeof url !== "string") {
|
|
4404
|
-
error(errors, `${
|
|
4404
|
+
error(errors, `${path29}/source/url`, "INVALID_IMAGE_URL", "image.source.url must be a string.");
|
|
4405
4405
|
}
|
|
4406
4406
|
return {
|
|
4407
4407
|
type: "image",
|
|
@@ -4417,13 +4417,13 @@ function validateBlock(value, path28, errors) {
|
|
|
4417
4417
|
const thinking = value["thinking"];
|
|
4418
4418
|
const signature = value["signature"];
|
|
4419
4419
|
if (typeof thinking !== "string") {
|
|
4420
|
-
error(errors, `${
|
|
4420
|
+
error(errors, `${path29}/thinking`, "INVALID_THINKING", "thinking.thinking must be a string.");
|
|
4421
4421
|
return void 0;
|
|
4422
4422
|
}
|
|
4423
4423
|
if (signature !== void 0 && typeof signature !== "string") {
|
|
4424
|
-
error(errors, `${
|
|
4424
|
+
error(errors, `${path29}/signature`, "INVALID_THINKING_SIGNATURE", "thinking.signature must be a string.");
|
|
4425
4425
|
}
|
|
4426
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4426
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path29}/providerMeta`, errors);
|
|
4427
4427
|
return {
|
|
4428
4428
|
type: "thinking",
|
|
4429
4429
|
thinking,
|
|
@@ -4432,7 +4432,7 @@ function validateBlock(value, path28, errors) {
|
|
|
4432
4432
|
};
|
|
4433
4433
|
}
|
|
4434
4434
|
default:
|
|
4435
|
-
error(errors, `${
|
|
4435
|
+
error(errors, `${path29}/type`, "UNKNOWN_BLOCK_TYPE", `Unknown content block type: ${String(type)}`);
|
|
4436
4436
|
return void 0;
|
|
4437
4437
|
}
|
|
4438
4438
|
}
|
|
@@ -4450,39 +4450,39 @@ function validateContextEditorMessages(value, currentMessageCount = 0) {
|
|
|
4450
4450
|
error(errors, "/messages", "PAYLOAD_TOO_LARGE", "Context editor payload is too large.");
|
|
4451
4451
|
}
|
|
4452
4452
|
value.forEach((item, index) => {
|
|
4453
|
-
const
|
|
4453
|
+
const path29 = `/messages/${index}`;
|
|
4454
4454
|
if (!isRecord3(item)) {
|
|
4455
|
-
error(errors,
|
|
4455
|
+
error(errors, path29, "INVALID_MESSAGE", "Message must be an object.");
|
|
4456
4456
|
return;
|
|
4457
4457
|
}
|
|
4458
4458
|
const role = item["role"];
|
|
4459
4459
|
if (!isMessageRole(role)) {
|
|
4460
|
-
error(errors, `${
|
|
4460
|
+
error(errors, `${path29}/role`, "INVALID_ROLE", "Message role must be user, assistant, or system.");
|
|
4461
4461
|
return;
|
|
4462
4462
|
}
|
|
4463
4463
|
const rawContent = item["content"];
|
|
4464
4464
|
let content;
|
|
4465
4465
|
if (typeof rawContent === "string") {
|
|
4466
4466
|
if (rawContent.length > MAX_STRING_LENGTH) {
|
|
4467
|
-
error(errors, `${
|
|
4467
|
+
error(errors, `${path29}/content`, "CONTENT_TOO_LARGE", "Message content is too large.");
|
|
4468
4468
|
return;
|
|
4469
4469
|
}
|
|
4470
4470
|
content = rawContent;
|
|
4471
4471
|
} else if (Array.isArray(rawContent)) {
|
|
4472
4472
|
const blocks = [];
|
|
4473
4473
|
rawContent.forEach((block, blockIndex) => {
|
|
4474
|
-
const parsed = validateBlock(block, `${
|
|
4474
|
+
const parsed = validateBlock(block, `${path29}/content/${blockIndex}`, errors);
|
|
4475
4475
|
if (parsed) blocks.push(parsed);
|
|
4476
4476
|
});
|
|
4477
4477
|
content = blocks;
|
|
4478
4478
|
} else {
|
|
4479
|
-
error(errors, `${
|
|
4479
|
+
error(errors, `${path29}/content`, "INVALID_CONTENT", "Message content must be a string or content block array.");
|
|
4480
4480
|
return;
|
|
4481
4481
|
}
|
|
4482
4482
|
const ts = item["ts"];
|
|
4483
4483
|
if (ts !== void 0) {
|
|
4484
4484
|
if (typeof ts !== "string" || Number.isNaN(Date.parse(ts))) {
|
|
4485
|
-
error(errors, `${
|
|
4485
|
+
error(errors, `${path29}/ts`, "INVALID_TIMESTAMP", "Message ts must be an ISO-like timestamp string.");
|
|
4486
4486
|
return;
|
|
4487
4487
|
}
|
|
4488
4488
|
}
|
|
@@ -4856,7 +4856,11 @@ function createConnectionLifecycle(options) {
|
|
|
4856
4856
|
}
|
|
4857
4857
|
|
|
4858
4858
|
// src/server/connections-health-route.ts
|
|
4859
|
-
import {
|
|
4859
|
+
import {
|
|
4860
|
+
ChronicleProjectServerClient,
|
|
4861
|
+
createChronicleProjectAccess as createChronicleProjectAccess2,
|
|
4862
|
+
resolveChronicleProjectServerOptions
|
|
4863
|
+
} from "@wrongstack/core/chronicle";
|
|
4860
4864
|
import {
|
|
4861
4865
|
isMailboxProjectServerAvailable,
|
|
4862
4866
|
MailboxProjectServerConnection
|
|
@@ -4864,7 +4868,11 @@ import {
|
|
|
4864
4868
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
|
|
4865
4869
|
import { getKanbanServerConnection } from "@wrongstack/kanban";
|
|
4866
4870
|
import { isSageProjectServerAvailable, SageProjectServerConnection } from "@wrongstack/sage";
|
|
4867
|
-
import {
|
|
4871
|
+
import {
|
|
4872
|
+
checkCodebaseIndexServerHealth,
|
|
4873
|
+
getIndexState,
|
|
4874
|
+
shutdownCodebaseIndexServer
|
|
4875
|
+
} from "@wrongstack/tools";
|
|
4868
4876
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
4869
4877
|
if (message.type !== "connections.health") return false;
|
|
4870
4878
|
try {
|
|
@@ -5341,9 +5349,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5341
5349
|
const cwd = projectRoot || void 0;
|
|
5342
5350
|
try {
|
|
5343
5351
|
const { execFile: ef } = await import("node:child_process");
|
|
5344
|
-
const git = (args) => new Promise((
|
|
5352
|
+
const git = (args) => new Promise((resolve15) => {
|
|
5345
5353
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
5346
|
-
|
|
5354
|
+
resolve15(err ? "" : stdout.trim());
|
|
5347
5355
|
});
|
|
5348
5356
|
});
|
|
5349
5357
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -5369,12 +5377,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5369
5377
|
function makeGit(cwd) {
|
|
5370
5378
|
return async (args) => {
|
|
5371
5379
|
const { execFile: ef } = await import("node:child_process");
|
|
5372
|
-
return new Promise((
|
|
5380
|
+
return new Promise((resolve15) => {
|
|
5373
5381
|
ef(
|
|
5374
5382
|
"git",
|
|
5375
5383
|
args,
|
|
5376
5384
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
5377
|
-
(err, stdout) =>
|
|
5385
|
+
(err, stdout) => resolve15(err ? "" : stdout)
|
|
5378
5386
|
);
|
|
5379
5387
|
});
|
|
5380
5388
|
};
|
|
@@ -5398,15 +5406,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5398
5406
|
if (!m) continue;
|
|
5399
5407
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5400
5408
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5401
|
-
let
|
|
5402
|
-
if (
|
|
5409
|
+
let path29 = m[3] ?? "";
|
|
5410
|
+
if (path29 === "") {
|
|
5403
5411
|
i += 1;
|
|
5404
|
-
|
|
5412
|
+
path29 = parts[i + 1] ?? parts[i] ?? "";
|
|
5405
5413
|
i += 1;
|
|
5406
5414
|
}
|
|
5407
|
-
if (!
|
|
5408
|
-
const prev = counts.get(
|
|
5409
|
-
counts.set(
|
|
5415
|
+
if (!path29) continue;
|
|
5416
|
+
const prev = counts.get(path29) ?? { added: 0, deleted: 0 };
|
|
5417
|
+
counts.set(path29, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5410
5418
|
}
|
|
5411
5419
|
};
|
|
5412
5420
|
parseNumstat(unstagedNumstat);
|
|
@@ -5418,7 +5426,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5418
5426
|
if (!rec || rec.length < 3) continue;
|
|
5419
5427
|
const x = rec[0] ?? " ";
|
|
5420
5428
|
const y = rec[1] ?? " ";
|
|
5421
|
-
const
|
|
5429
|
+
const path29 = rec.slice(3);
|
|
5422
5430
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5423
5431
|
if (isRename) i += 1;
|
|
5424
5432
|
let status;
|
|
@@ -5430,13 +5438,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5430
5438
|
else if (x === "D" || y === "D") status = "D";
|
|
5431
5439
|
else status = "M";
|
|
5432
5440
|
const staged = x !== " " && x !== "?";
|
|
5433
|
-
let added = counts.get(
|
|
5434
|
-
let deleted = counts.get(
|
|
5441
|
+
let added = counts.get(path29)?.added ?? 0;
|
|
5442
|
+
let deleted = counts.get(path29)?.deleted ?? 0;
|
|
5435
5443
|
if (status === "?") {
|
|
5436
5444
|
added = 0;
|
|
5437
5445
|
deleted = 0;
|
|
5438
5446
|
}
|
|
5439
|
-
files.push({ path:
|
|
5447
|
+
files.push({ path: path29, status, added, deleted, staged });
|
|
5440
5448
|
}
|
|
5441
5449
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5442
5450
|
} catch (err) {
|
|
@@ -5447,10 +5455,10 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5447
5455
|
}
|
|
5448
5456
|
}
|
|
5449
5457
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5450
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5458
|
+
async function handleGitDiff(ws, projectRoot, path29) {
|
|
5451
5459
|
const cwd = projectRoot || void 0;
|
|
5452
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5453
|
-
if (!
|
|
5460
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path29, ...extra } });
|
|
5461
|
+
if (!path29 || path29.includes("\0") || path29.includes("..") || nodePath.isAbsolute(path29)) {
|
|
5454
5462
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5455
5463
|
return;
|
|
5456
5464
|
}
|
|
@@ -5458,10 +5466,10 @@ async function handleGitDiff(ws, projectRoot, path28) {
|
|
|
5458
5466
|
const git = makeGit(cwd);
|
|
5459
5467
|
const { readFile: readFile11 } = await import("node:fs/promises");
|
|
5460
5468
|
const { join: join15 } = await import("node:path");
|
|
5461
|
-
const oldText = await git(["show", `HEAD:${
|
|
5469
|
+
const oldText = await git(["show", `HEAD:${path29}`]);
|
|
5462
5470
|
let newText = "";
|
|
5463
5471
|
try {
|
|
5464
|
-
const abs = cwd ? join15(cwd,
|
|
5472
|
+
const abs = cwd ? join15(cwd, path29) : path29;
|
|
5465
5473
|
const buf = await readFile11(abs);
|
|
5466
5474
|
if (buf.includes(0)) {
|
|
5467
5475
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5541,7 +5549,7 @@ import { execFile } from "node:child_process";
|
|
|
5541
5549
|
var GIT_TIMEOUT_MS = 1e4;
|
|
5542
5550
|
var GIT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
5543
5551
|
function gitStdout(cwd, args) {
|
|
5544
|
-
return new Promise((
|
|
5552
|
+
return new Promise((resolve15) => {
|
|
5545
5553
|
execFile(
|
|
5546
5554
|
"git",
|
|
5547
5555
|
[...args],
|
|
@@ -5552,7 +5560,7 @@ function gitStdout(cwd, args) {
|
|
|
5552
5560
|
timeout: GIT_TIMEOUT_MS,
|
|
5553
5561
|
maxBuffer: GIT_MAX_OUTPUT_BYTES
|
|
5554
5562
|
},
|
|
5555
|
-
(error2, stdout) =>
|
|
5563
|
+
(error2, stdout) => resolve15(error2 ? null : stdout)
|
|
5556
5564
|
);
|
|
5557
5565
|
});
|
|
5558
5566
|
}
|
|
@@ -5818,13 +5826,13 @@ var GoalWebSocketHandler = class {
|
|
|
5818
5826
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
5819
5827
|
try {
|
|
5820
5828
|
const { exec } = await import("node:child_process");
|
|
5821
|
-
const result = await new Promise((
|
|
5829
|
+
const result = await new Promise((resolve15) => {
|
|
5822
5830
|
exec("npx tsc --noEmit", { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
5823
5831
|
if (err && err.code === "ENOENT") {
|
|
5824
|
-
|
|
5832
|
+
resolve15("[verify] tsc not found \u2014 skipping");
|
|
5825
5833
|
return;
|
|
5826
5834
|
}
|
|
5827
|
-
|
|
5835
|
+
resolve15(stdout + stderr);
|
|
5828
5836
|
});
|
|
5829
5837
|
});
|
|
5830
5838
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
@@ -6503,7 +6511,7 @@ function pushEvent(event) {
|
|
|
6503
6511
|
}
|
|
6504
6512
|
}
|
|
6505
6513
|
function parseBody(req) {
|
|
6506
|
-
return new Promise((
|
|
6514
|
+
return new Promise((resolve15, reject) => {
|
|
6507
6515
|
let body = "";
|
|
6508
6516
|
let bodyBytes = 0;
|
|
6509
6517
|
let tooLarge = false;
|
|
@@ -6523,7 +6531,7 @@ function parseBody(req) {
|
|
|
6523
6531
|
return;
|
|
6524
6532
|
}
|
|
6525
6533
|
try {
|
|
6526
|
-
|
|
6534
|
+
resolve15(JSON.parse(body));
|
|
6527
6535
|
} catch {
|
|
6528
6536
|
reject(new Error("Invalid JSON"));
|
|
6529
6537
|
}
|
|
@@ -6600,7 +6608,7 @@ async function handleApiAnalyticsSummary(res) {
|
|
|
6600
6608
|
// src/server/http-server.ts
|
|
6601
6609
|
import * as fs9 from "node:fs/promises";
|
|
6602
6610
|
import * as http from "node:http";
|
|
6603
|
-
import * as
|
|
6611
|
+
import * as path12 from "node:path";
|
|
6604
6612
|
import * as v8 from "node:v8";
|
|
6605
6613
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6606
6614
|
|
|
@@ -6680,6 +6688,156 @@ async function handleCodemapSymbols(res, deps2, file) {
|
|
|
6680
6688
|
);
|
|
6681
6689
|
}
|
|
6682
6690
|
|
|
6691
|
+
// src/server/deadcode-handlers.ts
|
|
6692
|
+
import * as path10 from "node:path";
|
|
6693
|
+
import { runDeadCodeScan } from "@wrongstack/tools/codebase-index";
|
|
6694
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
6695
|
+
function readJsonBody(req) {
|
|
6696
|
+
return new Promise((resolve15, reject) => {
|
|
6697
|
+
const chunks = [];
|
|
6698
|
+
let total = 0;
|
|
6699
|
+
req.on("data", (chunk) => {
|
|
6700
|
+
total += chunk.length;
|
|
6701
|
+
if (total > MAX_BODY_BYTES) {
|
|
6702
|
+
req.destroy(new Error("Request body too large"));
|
|
6703
|
+
reject(new Error("Request body exceeds 10 MiB limit"));
|
|
6704
|
+
return;
|
|
6705
|
+
}
|
|
6706
|
+
chunks.push(chunk);
|
|
6707
|
+
});
|
|
6708
|
+
req.on("end", () => resolve15(Buffer.concat(chunks).toString("utf8")));
|
|
6709
|
+
req.on("error", (err) => reject(err));
|
|
6710
|
+
});
|
|
6711
|
+
}
|
|
6712
|
+
async function handleDeadCodeScan(res, deps2, req) {
|
|
6713
|
+
try {
|
|
6714
|
+
let body = {};
|
|
6715
|
+
const raw = await readJsonBody(req);
|
|
6716
|
+
if (raw) {
|
|
6717
|
+
try {
|
|
6718
|
+
body = JSON.parse(raw);
|
|
6719
|
+
} catch {
|
|
6720
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6721
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
6722
|
+
return;
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
const scanIndexDir = body.indexDir ?? deps2.indexDir;
|
|
6726
|
+
if (scanIndexDir) {
|
|
6727
|
+
const resolvedRoot = path10.resolve(deps2.projectRoot);
|
|
6728
|
+
const resolvedIndex = path10.resolve(deps2.projectRoot, scanIndexDir);
|
|
6729
|
+
if (resolvedIndex !== resolvedRoot && !resolvedIndex.startsWith(resolvedRoot + path10.sep)) {
|
|
6730
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6731
|
+
res.end(JSON.stringify({ error: "Invalid indexDir: must be within project root" }));
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
const result = runDeadCodeScan(deps2.projectRoot, {
|
|
6736
|
+
indexDir: scanIndexDir,
|
|
6737
|
+
userEntryPoints: body.entryPoints
|
|
6738
|
+
});
|
|
6739
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6740
|
+
res.end(JSON.stringify(result));
|
|
6741
|
+
} catch (err) {
|
|
6742
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6743
|
+
res.end(
|
|
6744
|
+
JSON.stringify({
|
|
6745
|
+
error: "Dead-code scan failed",
|
|
6746
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
6747
|
+
})
|
|
6748
|
+
);
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
function handleDeadCodeActionPlan(res, _deps, req) {
|
|
6752
|
+
return readJsonBody(req).then((raw) => {
|
|
6753
|
+
let parsed;
|
|
6754
|
+
try {
|
|
6755
|
+
parsed = JSON.parse(raw);
|
|
6756
|
+
} catch {
|
|
6757
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6758
|
+
res.end(JSON.stringify({ error: "Invalid scan result JSON" }));
|
|
6759
|
+
return;
|
|
6760
|
+
}
|
|
6761
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.deadPackages) || !Array.isArray(parsed.deadFiles) || !Array.isArray(parsed.deadSymbols)) {
|
|
6762
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6763
|
+
res.end(
|
|
6764
|
+
JSON.stringify({
|
|
6765
|
+
error: "Invalid scan result: missing or malformed required fields (deadPackages, deadFiles, deadSymbols)"
|
|
6766
|
+
})
|
|
6767
|
+
);
|
|
6768
|
+
return;
|
|
6769
|
+
}
|
|
6770
|
+
const result = parsed;
|
|
6771
|
+
const plan = buildActionPlan(result);
|
|
6772
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6773
|
+
res.end(JSON.stringify(plan));
|
|
6774
|
+
}).catch((err) => {
|
|
6775
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6776
|
+
res.end(
|
|
6777
|
+
JSON.stringify({
|
|
6778
|
+
error: "Failed to read request body",
|
|
6779
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
6780
|
+
})
|
|
6781
|
+
);
|
|
6782
|
+
});
|
|
6783
|
+
}
|
|
6784
|
+
function buildActionPlan(result) {
|
|
6785
|
+
const files = /* @__PURE__ */ new Map();
|
|
6786
|
+
for (const dp of result.deadPackages) {
|
|
6787
|
+
const pseudoFile = {
|
|
6788
|
+
file: `${dp.package}/ (package)`,
|
|
6789
|
+
symbolCount: dp.fileCount,
|
|
6790
|
+
symbols: [`remove package ${dp.package} (${dp.fileCount} files, path: ${dp.path})`],
|
|
6791
|
+
priority: 0
|
|
6792
|
+
};
|
|
6793
|
+
files.set(pseudoFile.file, pseudoFile);
|
|
6794
|
+
}
|
|
6795
|
+
for (const df of result.deadFiles) {
|
|
6796
|
+
const existing = files.get(df.file);
|
|
6797
|
+
if (existing) {
|
|
6798
|
+
if (existing.priority > 1) existing.priority = 1;
|
|
6799
|
+
existing.symbolCount += df.symbolCount;
|
|
6800
|
+
continue;
|
|
6801
|
+
}
|
|
6802
|
+
files.set(df.file, {
|
|
6803
|
+
file: df.file,
|
|
6804
|
+
symbolCount: df.symbolCount,
|
|
6805
|
+
symbols: [`entire file (${df.symbolCount} symbols) is dead`],
|
|
6806
|
+
priority: 1
|
|
6807
|
+
});
|
|
6808
|
+
}
|
|
6809
|
+
const deadInAliveFiles = /* @__PURE__ */ new Map();
|
|
6810
|
+
const deadFileSet = new Set(result.deadFiles.map((df) => df.file));
|
|
6811
|
+
for (const ds of result.deadSymbols) {
|
|
6812
|
+
if (deadFileSet.has(ds.file)) continue;
|
|
6813
|
+
const list = deadInAliveFiles.get(ds.file) ?? [];
|
|
6814
|
+
list.push(`${ds.kind} ${ds.name} (line ${ds.line})`);
|
|
6815
|
+
deadInAliveFiles.set(ds.file, list);
|
|
6816
|
+
}
|
|
6817
|
+
for (const [file, symbols] of deadInAliveFiles) {
|
|
6818
|
+
const existing = files.get(file);
|
|
6819
|
+
if (existing) {
|
|
6820
|
+
existing.symbols.push(...symbols);
|
|
6821
|
+
existing.symbolCount += symbols.length;
|
|
6822
|
+
continue;
|
|
6823
|
+
}
|
|
6824
|
+
files.set(file, {
|
|
6825
|
+
file,
|
|
6826
|
+
symbolCount: symbols.length,
|
|
6827
|
+
symbols,
|
|
6828
|
+
priority: 2
|
|
6829
|
+
});
|
|
6830
|
+
}
|
|
6831
|
+
const sorted = [...files.values()].sort((a, b) => a.priority - b.priority || a.file.localeCompare(b.file));
|
|
6832
|
+
return {
|
|
6833
|
+
summary: `Dead-code scan found ${result.stats.dead} dead symbols across ${result.deadFiles.length} dead files and ${result.deadPackages.length} dead packages. Action plan has ${sorted.length} file group(s) to address.`,
|
|
6834
|
+
files: sorted,
|
|
6835
|
+
totalDeadSymbols: result.stats.dead,
|
|
6836
|
+
totalDeadFiles: result.deadFiles.length,
|
|
6837
|
+
totalDeadPackages: result.deadPackages.length
|
|
6838
|
+
};
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6683
6841
|
// src/server/http-server/api-handlers.ts
|
|
6684
6842
|
async function handleApiSessions(res, globalRoot) {
|
|
6685
6843
|
if (!globalRoot) {
|
|
@@ -6901,8 +7059,8 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
6901
7059
|
res.end(JSON.stringify({ error: String(err) }));
|
|
6902
7060
|
}
|
|
6903
7061
|
}
|
|
6904
|
-
function
|
|
6905
|
-
return new Promise((
|
|
7062
|
+
function readJsonBody2(req) {
|
|
7063
|
+
return new Promise((resolve15, reject) => {
|
|
6906
7064
|
let data = "";
|
|
6907
7065
|
req.on("data", (chunk) => {
|
|
6908
7066
|
data += chunk;
|
|
@@ -6913,7 +7071,7 @@ function readJsonBody(req) {
|
|
|
6913
7071
|
});
|
|
6914
7072
|
req.on("end", () => {
|
|
6915
7073
|
try {
|
|
6916
|
-
|
|
7074
|
+
resolve15(data ? JSON.parse(data) : {});
|
|
6917
7075
|
} catch (err) {
|
|
6918
7076
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
6919
7077
|
}
|
|
@@ -6929,7 +7087,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
6929
7087
|
}
|
|
6930
7088
|
let body;
|
|
6931
7089
|
try {
|
|
6932
|
-
body = await
|
|
7090
|
+
body = await readJsonBody2(req);
|
|
6933
7091
|
} catch {
|
|
6934
7092
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6935
7093
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7031,7 +7189,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
7031
7189
|
}
|
|
7032
7190
|
let body = {};
|
|
7033
7191
|
try {
|
|
7034
|
-
body = await
|
|
7192
|
+
body = await readJsonBody2(req);
|
|
7035
7193
|
} catch {
|
|
7036
7194
|
}
|
|
7037
7195
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
@@ -7072,7 +7230,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7072
7230
|
}
|
|
7073
7231
|
let body;
|
|
7074
7232
|
try {
|
|
7075
|
-
body = await
|
|
7233
|
+
body = await readJsonBody2(req);
|
|
7076
7234
|
} catch {
|
|
7077
7235
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7078
7236
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7136,12 +7294,12 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7136
7294
|
|
|
7137
7295
|
// src/server/projects-manifest.ts
|
|
7138
7296
|
import * as fs8 from "node:fs/promises";
|
|
7139
|
-
import * as
|
|
7297
|
+
import * as path11 from "node:path";
|
|
7140
7298
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7141
7299
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7142
7300
|
function projectsJsonPath(globalConfigPath) {
|
|
7143
|
-
const base =
|
|
7144
|
-
return
|
|
7301
|
+
const base = path11.dirname(globalConfigPath);
|
|
7302
|
+
return path11.join(base, "projects.json");
|
|
7145
7303
|
}
|
|
7146
7304
|
async function loadManifest(globalConfigPath) {
|
|
7147
7305
|
try {
|
|
@@ -7154,37 +7312,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7154
7312
|
}
|
|
7155
7313
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7156
7314
|
const file = projectsJsonPath(globalConfigPath);
|
|
7157
|
-
await fs8.mkdir(
|
|
7315
|
+
await fs8.mkdir(path11.dirname(file), { recursive: true });
|
|
7158
7316
|
await fs8.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7159
7317
|
}
|
|
7160
7318
|
function generateProjectSlug(rootPath) {
|
|
7161
7319
|
return projectSlug(rootPath);
|
|
7162
7320
|
}
|
|
7163
7321
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7164
|
-
const base =
|
|
7165
|
-
const dir =
|
|
7322
|
+
const base = path11.dirname(globalConfigPath);
|
|
7323
|
+
const dir = path11.join(base, "projects", slug);
|
|
7166
7324
|
await fs8.mkdir(dir, { recursive: true });
|
|
7167
7325
|
return dir;
|
|
7168
7326
|
}
|
|
7169
7327
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7170
|
-
const root =
|
|
7328
|
+
const root = path11.resolve(options.projectRoot);
|
|
7171
7329
|
const file = projectsJsonPath(globalConfigPath);
|
|
7172
7330
|
let entry;
|
|
7173
7331
|
await withFileLock(file, async () => {
|
|
7174
7332
|
const manifest = await loadManifest(globalConfigPath);
|
|
7175
7333
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7176
|
-
entry = manifest.projects.find((candidate) =>
|
|
7334
|
+
entry = manifest.projects.find((candidate) => path11.resolve(candidate.root) === root);
|
|
7177
7335
|
if (entry) {
|
|
7178
7336
|
entry.lastSeen = now;
|
|
7179
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7337
|
+
if (options.workingDir) entry.lastWorkingDir = path11.resolve(options.workingDir);
|
|
7180
7338
|
} else {
|
|
7181
7339
|
entry = {
|
|
7182
|
-
name: options.name ??
|
|
7340
|
+
name: options.name ?? path11.basename(root),
|
|
7183
7341
|
root,
|
|
7184
7342
|
slug: generateProjectSlug(root),
|
|
7185
7343
|
createdAt: now,
|
|
7186
7344
|
lastSeen: now,
|
|
7187
|
-
lastWorkingDir: options.workingDir ?
|
|
7345
|
+
lastWorkingDir: options.workingDir ? path11.resolve(options.workingDir) : void 0
|
|
7188
7346
|
};
|
|
7189
7347
|
manifest.projects.push(entry);
|
|
7190
7348
|
}
|
|
@@ -7595,9 +7753,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
7595
7753
|
return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
7596
7754
|
}
|
|
7597
7755
|
function isInsideDist(candidate, distDir) {
|
|
7598
|
-
const root =
|
|
7599
|
-
const resolved =
|
|
7600
|
-
return resolved === root || resolved.startsWith(root +
|
|
7756
|
+
const root = path12.resolve(distDir);
|
|
7757
|
+
const resolved = path12.resolve(candidate);
|
|
7758
|
+
return resolved === root || resolved.startsWith(root + path12.sep);
|
|
7601
7759
|
}
|
|
7602
7760
|
function decodeSessionId(segment) {
|
|
7603
7761
|
try {
|
|
@@ -7617,7 +7775,7 @@ function strictDecodeParam(segment, res) {
|
|
|
7617
7775
|
}
|
|
7618
7776
|
function createHttpServer(opts) {
|
|
7619
7777
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
7620
|
-
const distDir =
|
|
7778
|
+
const distDir = path12.resolve(opts.distDir);
|
|
7621
7779
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
7622
7780
|
let techStackRuntime = null;
|
|
7623
7781
|
const getTechStackRuntime = async () => {
|
|
@@ -7836,6 +7994,42 @@ function createHttpServer(opts) {
|
|
|
7836
7994
|
);
|
|
7837
7995
|
return;
|
|
7838
7996
|
}
|
|
7997
|
+
if (url.pathname === "/api/deadcode/scan" && req.method === "POST") {
|
|
7998
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
7999
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8000
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8001
|
+
return;
|
|
8002
|
+
}
|
|
8003
|
+
if (!opts.projectRoot) {
|
|
8004
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8005
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8006
|
+
return;
|
|
8007
|
+
}
|
|
8008
|
+
const deadCodeDeps = {
|
|
8009
|
+
projectRoot: opts.projectRoot,
|
|
8010
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8011
|
+
};
|
|
8012
|
+
await handleDeadCodeScan(res, deadCodeDeps, req);
|
|
8013
|
+
return;
|
|
8014
|
+
}
|
|
8015
|
+
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
8016
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8017
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8018
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8019
|
+
return;
|
|
8020
|
+
}
|
|
8021
|
+
if (!opts.projectRoot) {
|
|
8022
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8023
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8024
|
+
return;
|
|
8025
|
+
}
|
|
8026
|
+
const deadCodeDeps = {
|
|
8027
|
+
projectRoot: opts.projectRoot,
|
|
8028
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8029
|
+
};
|
|
8030
|
+
await handleDeadCodeActionPlan(res, deadCodeDeps, req);
|
|
8031
|
+
return;
|
|
8032
|
+
}
|
|
7839
8033
|
if (url.pathname.startsWith("/api/techstack/")) {
|
|
7840
8034
|
if (requireAccessToken && !accessTokenOk) {
|
|
7841
8035
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -7968,17 +8162,17 @@ function createHttpServer(opts) {
|
|
|
7968
8162
|
}
|
|
7969
8163
|
let filePath;
|
|
7970
8164
|
if (url.pathname === "/" || url.pathname === "") {
|
|
7971
|
-
filePath =
|
|
8165
|
+
filePath = path12.join(distDir, "index.html");
|
|
7972
8166
|
} else {
|
|
7973
|
-
filePath =
|
|
8167
|
+
filePath = path12.join(distDir, url.pathname);
|
|
7974
8168
|
}
|
|
7975
|
-
const resolvedPath =
|
|
8169
|
+
const resolvedPath = path12.resolve(filePath);
|
|
7976
8170
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
7977
8171
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
7978
8172
|
res.end("Forbidden");
|
|
7979
8173
|
return;
|
|
7980
8174
|
}
|
|
7981
|
-
const ext =
|
|
8175
|
+
const ext = path12.extname(resolvedPath);
|
|
7982
8176
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
7983
8177
|
res.setHeader("Content-Type", contentType);
|
|
7984
8178
|
setStaticSecurityHeaders(res);
|
|
@@ -8002,7 +8196,7 @@ function createHttpServer(opts) {
|
|
|
8002
8196
|
} catch (err) {
|
|
8003
8197
|
if (err.code === "ENOENT") {
|
|
8004
8198
|
try {
|
|
8005
|
-
const html = await fs9.readFile(
|
|
8199
|
+
const html = await fs9.readFile(path12.join(distDir, "index.html"), "utf8");
|
|
8006
8200
|
setStaticSecurityHeaders(res);
|
|
8007
8201
|
res.writeHead(200, {
|
|
8008
8202
|
"Content-Type": "text/html",
|
|
@@ -8032,14 +8226,14 @@ function createHttpServer(opts) {
|
|
|
8032
8226
|
|
|
8033
8227
|
// src/server/instance-registry.ts
|
|
8034
8228
|
import * as os from "node:os";
|
|
8035
|
-
import * as
|
|
8229
|
+
import * as path13 from "node:path";
|
|
8036
8230
|
import * as fs10 from "node:fs/promises";
|
|
8037
8231
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8038
8232
|
function defaultBaseDir() {
|
|
8039
|
-
return
|
|
8233
|
+
return path13.join(os.homedir(), ".wrongstack");
|
|
8040
8234
|
}
|
|
8041
8235
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8042
|
-
return
|
|
8236
|
+
return path13.join(baseDir, "webui-instances.json");
|
|
8043
8237
|
}
|
|
8044
8238
|
function isPidAlive(pid) {
|
|
8045
8239
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -11402,16 +11596,16 @@ function createModelOperations(context) {
|
|
|
11402
11596
|
import * as net from "node:net";
|
|
11403
11597
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
11404
11598
|
function isPortFree(host, port) {
|
|
11405
|
-
return new Promise((
|
|
11599
|
+
return new Promise((resolve15) => {
|
|
11406
11600
|
const srv = net.createServer();
|
|
11407
|
-
srv.once("error", () =>
|
|
11601
|
+
srv.once("error", () => resolve15(false));
|
|
11408
11602
|
srv.once("listening", () => {
|
|
11409
|
-
srv.close(() =>
|
|
11603
|
+
srv.close(() => resolve15(true));
|
|
11410
11604
|
});
|
|
11411
11605
|
try {
|
|
11412
11606
|
srv.listen(port, host);
|
|
11413
11607
|
} catch {
|
|
11414
|
-
|
|
11608
|
+
resolve15(false);
|
|
11415
11609
|
}
|
|
11416
11610
|
});
|
|
11417
11611
|
}
|
|
@@ -11703,7 +11897,7 @@ function seedContextMeta(config, context) {
|
|
|
11703
11897
|
meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
|
|
11704
11898
|
meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
|
|
11705
11899
|
meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
|
|
11706
|
-
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] :
|
|
11900
|
+
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 15e3;
|
|
11707
11901
|
meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
|
|
11708
11902
|
meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
|
|
11709
11903
|
const cascade = autoReviewExt?.["cascadeOn"];
|
|
@@ -11723,7 +11917,7 @@ function seedContextMeta(config, context) {
|
|
|
11723
11917
|
|
|
11724
11918
|
// src/server/pref-helpers.ts
|
|
11725
11919
|
import * as fs12 from "node:fs/promises";
|
|
11726
|
-
import * as
|
|
11920
|
+
import * as path14 from "node:path";
|
|
11727
11921
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
11728
11922
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
11729
11923
|
var PREF_KEYS = [
|
|
@@ -11819,7 +12013,7 @@ function prefSnapshot(contextMeta) {
|
|
|
11819
12013
|
return snapshot;
|
|
11820
12014
|
}
|
|
11821
12015
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
11822
|
-
const globalRoot =
|
|
12016
|
+
const globalRoot = path14.dirname(filePath);
|
|
11823
12017
|
await backupConfigFile(filePath, { globalRoot });
|
|
11824
12018
|
let raw;
|
|
11825
12019
|
try {
|
|
@@ -12299,7 +12493,7 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
12299
12493
|
|
|
12300
12494
|
// src/server/project-handlers.ts
|
|
12301
12495
|
import * as fs13 from "node:fs/promises";
|
|
12302
|
-
import * as
|
|
12496
|
+
import * as path15 from "node:path";
|
|
12303
12497
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
12304
12498
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
12305
12499
|
function createProjectHandlers(ctx) {
|
|
@@ -12351,8 +12545,8 @@ function createProjectHandlers(ctx) {
|
|
|
12351
12545
|
});
|
|
12352
12546
|
return;
|
|
12353
12547
|
}
|
|
12354
|
-
const resolved =
|
|
12355
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12548
|
+
const resolved = path15.resolve(parsed.value.root);
|
|
12549
|
+
const name2 = parsed.value.name?.trim() || path15.basename(resolved);
|
|
12356
12550
|
try {
|
|
12357
12551
|
const stat3 = await fs13.stat(resolved).catch(() => null);
|
|
12358
12552
|
if (!stat3?.isDirectory()) {
|
|
@@ -12363,7 +12557,7 @@ function createProjectHandlers(ctx) {
|
|
|
12363
12557
|
return;
|
|
12364
12558
|
}
|
|
12365
12559
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
12366
|
-
const already = before.projects.some((project) =>
|
|
12560
|
+
const already = before.projects.some((project) => path15.resolve(project.root) === resolved);
|
|
12367
12561
|
const entry = await touchProjectInManifest(
|
|
12368
12562
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
12369
12563
|
ctx.globalConfigPath
|
|
@@ -12393,8 +12587,8 @@ function createProjectHandlers(ctx) {
|
|
|
12393
12587
|
});
|
|
12394
12588
|
return;
|
|
12395
12589
|
}
|
|
12396
|
-
const resolved =
|
|
12397
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12590
|
+
const resolved = path15.resolve(parsed.value.root);
|
|
12591
|
+
const name2 = parsed.value.name?.trim() || path15.basename(resolved);
|
|
12398
12592
|
if (!ctx.allowProjectMutations) {
|
|
12399
12593
|
sendTo(ws, {
|
|
12400
12594
|
type: "projects.selected",
|
|
@@ -12429,6 +12623,17 @@ function createProjectHandlers(ctx) {
|
|
|
12429
12623
|
});
|
|
12430
12624
|
const previous = ctx.getSession();
|
|
12431
12625
|
const previousId = previous.id;
|
|
12626
|
+
const previousProjectRoot = ctx.getProjectRoot();
|
|
12627
|
+
const previousPaths = resolveWstackPaths4({
|
|
12628
|
+
projectRoot: previousProjectRoot,
|
|
12629
|
+
globalRoot: ctx.wpaths.globalRoot
|
|
12630
|
+
});
|
|
12631
|
+
const previousIdentityTarget = {
|
|
12632
|
+
projectSlug: previousPaths.projectSlug,
|
|
12633
|
+
projectRoot: previousProjectRoot,
|
|
12634
|
+
projectName: path15.basename(previousProjectRoot),
|
|
12635
|
+
workingDir: ctx.context.workingDir
|
|
12636
|
+
};
|
|
12432
12637
|
const previousUsage = ctx.tokenCounter.total();
|
|
12433
12638
|
const config = ctx.getConfig?.() ?? ctx.config;
|
|
12434
12639
|
const next = await store.create({
|
|
@@ -12454,7 +12659,16 @@ function createProjectHandlers(ctx) {
|
|
|
12454
12659
|
};
|
|
12455
12660
|
try {
|
|
12456
12661
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
12662
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
12457
12663
|
} catch (err) {
|
|
12664
|
+
try {
|
|
12665
|
+
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
12666
|
+
} catch {
|
|
12667
|
+
}
|
|
12668
|
+
try {
|
|
12669
|
+
await ctx.onSessionSwapped?.(previous.id, previousIdentityTarget);
|
|
12670
|
+
} catch {
|
|
12671
|
+
}
|
|
12458
12672
|
await next.close().catch(() => void 0);
|
|
12459
12673
|
await store.delete(next.id).catch(() => void 0);
|
|
12460
12674
|
throw err;
|
|
@@ -13477,6 +13691,7 @@ var CLIENT_WORKSPACE_MESSAGE_TYPES = [
|
|
|
13477
13691
|
var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
|
|
13478
13692
|
"codebase.index.server.shutdown",
|
|
13479
13693
|
"connections.health",
|
|
13694
|
+
"connections.service_action",
|
|
13480
13695
|
"diag.get",
|
|
13481
13696
|
"key.add",
|
|
13482
13697
|
"key.delete",
|
|
@@ -13750,6 +13965,7 @@ var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
|
13750
13965
|
"codebase.index.server.shutdown_result",
|
|
13751
13966
|
"connections.health_error",
|
|
13752
13967
|
"connections.health_result",
|
|
13968
|
+
"connections.service_action_result",
|
|
13753
13969
|
"diag.get",
|
|
13754
13970
|
"key.operation_result",
|
|
13755
13971
|
"model.switch_result",
|
|
@@ -13794,13 +14010,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
13794
14010
|
// src/protocol/decoder.ts
|
|
13795
14011
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
13796
14012
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
13797
|
-
function inspectValue(value,
|
|
14013
|
+
function inspectValue(value, path29, depth) {
|
|
13798
14014
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
13799
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
14015
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path29 };
|
|
13800
14016
|
}
|
|
13801
14017
|
if (value === null || typeof value !== "object") return null;
|
|
13802
14018
|
for (const key of Object.keys(value)) {
|
|
13803
|
-
const childPath = `${
|
|
14019
|
+
const childPath = `${path29}.${key}`;
|
|
13804
14020
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
13805
14021
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
13806
14022
|
}
|
|
@@ -14010,6 +14226,7 @@ function createSessionHandlers(ctx) {
|
|
|
14010
14226
|
ctx.context.session = next;
|
|
14011
14227
|
ctx.context.state.replaceMessages(messages);
|
|
14012
14228
|
await ctx.context.flushConversationJournal?.();
|
|
14229
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, sessionsDirectory());
|
|
14013
14230
|
ctx.context.state.replaceTodos(todos);
|
|
14014
14231
|
resetContextAccounting();
|
|
14015
14232
|
ctx.context.readFiles.clear();
|
|
@@ -14406,7 +14623,10 @@ function createSessionHandlers(ctx) {
|
|
|
14406
14623
|
rollbackClaim = await ctx.claimSession?.(canonicalId);
|
|
14407
14624
|
const resumed = await store.resume(canonicalId);
|
|
14408
14625
|
const restoredTodos = await loadTodosCheckpoint(
|
|
14409
|
-
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json")
|
|
14626
|
+
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json"),
|
|
14627
|
+
ctx.events,
|
|
14628
|
+
ctx.context.traceId,
|
|
14629
|
+
resumed.writer.id
|
|
14410
14630
|
).catch(() => null) ?? [];
|
|
14411
14631
|
activated = true;
|
|
14412
14632
|
await activateSession(
|
|
@@ -14925,7 +15145,7 @@ ${String(p.content ?? "")}`;
|
|
|
14925
15145
|
};
|
|
14926
15146
|
|
|
14927
15147
|
// src/server/codebase-index-server-control.ts
|
|
14928
|
-
import { shutdownCodebaseIndexServer } from "@wrongstack/tools";
|
|
15148
|
+
import { shutdownCodebaseIndexServer as shutdownCodebaseIndexServer2 } from "@wrongstack/tools";
|
|
14929
15149
|
async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
14930
15150
|
if (message.type !== "codebase.index.server.shutdown") return false;
|
|
14931
15151
|
const requestId = message.payload && typeof message.payload === "object" && typeof message.payload.requestId === "string" ? message.payload.requestId : "";
|
|
@@ -14952,7 +15172,7 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
14952
15172
|
});
|
|
14953
15173
|
return true;
|
|
14954
15174
|
}
|
|
14955
|
-
const result = await
|
|
15175
|
+
const result = await shutdownCodebaseIndexServer2(
|
|
14956
15176
|
projectRoot,
|
|
14957
15177
|
deps2.getIndexDir(),
|
|
14958
15178
|
"websocket-request"
|
|
@@ -15493,7 +15713,7 @@ function createRouteFamilyDispatcher(options) {
|
|
|
15493
15713
|
|
|
15494
15714
|
// src/server/shell-open.ts
|
|
15495
15715
|
import * as fs15 from "node:fs/promises";
|
|
15496
|
-
import * as
|
|
15716
|
+
import * as path16 from "node:path";
|
|
15497
15717
|
import { spawn } from "node:child_process";
|
|
15498
15718
|
function normalizeShellOpenTarget(target) {
|
|
15499
15719
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -15504,11 +15724,11 @@ function shellQuote(s) {
|
|
|
15504
15724
|
}
|
|
15505
15725
|
async function handleShellOpen(req, logger, options) {
|
|
15506
15726
|
try {
|
|
15507
|
-
const resolved =
|
|
15727
|
+
const resolved = path16.resolve(req.path);
|
|
15508
15728
|
if (options?.projectRoot) {
|
|
15509
|
-
const root =
|
|
15510
|
-
const relative5 =
|
|
15511
|
-
const escapes = relative5.startsWith("..") ||
|
|
15729
|
+
const root = path16.resolve(options.projectRoot);
|
|
15730
|
+
const relative5 = path16.relative(root, resolved);
|
|
15731
|
+
const escapes = relative5.startsWith("..") || path16.isAbsolute(relative5);
|
|
15512
15732
|
if (escapes) {
|
|
15513
15733
|
return {
|
|
15514
15734
|
success: false,
|
|
@@ -15570,6 +15790,7 @@ async function handleShellOpen(req, logger, options) {
|
|
|
15570
15790
|
import { listBoards as listBoards3 } from "@wrongstack/kanban";
|
|
15571
15791
|
import {
|
|
15572
15792
|
applySddLifecycle,
|
|
15793
|
+
extractVerificationCommand,
|
|
15573
15794
|
SddBoardStore
|
|
15574
15795
|
} from "@wrongstack/sdd";
|
|
15575
15796
|
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -15592,14 +15813,16 @@ var SddBoardWebSocketHandler = class {
|
|
|
15592
15813
|
store;
|
|
15593
15814
|
clients = /* @__PURE__ */ new Set();
|
|
15594
15815
|
lifecycle;
|
|
15816
|
+
security;
|
|
15595
15817
|
diskPollingEnabled;
|
|
15596
15818
|
latest = null;
|
|
15597
15819
|
poll = null;
|
|
15598
15820
|
pollInFlight = false;
|
|
15599
15821
|
unsub = null;
|
|
15600
|
-
constructor(boardsDir, events, lifecycle) {
|
|
15822
|
+
constructor(boardsDir, events, lifecycle, security) {
|
|
15601
15823
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
15602
15824
|
this.lifecycle = lifecycle;
|
|
15825
|
+
this.security = security;
|
|
15603
15826
|
this.diskPollingEnabled = events === void 0;
|
|
15604
15827
|
if (events) {
|
|
15605
15828
|
const handler = (e) => {
|
|
@@ -15645,6 +15868,43 @@ var SddBoardWebSocketHandler = class {
|
|
|
15645
15868
|
return;
|
|
15646
15869
|
}
|
|
15647
15870
|
if (CONTROL_TYPES.has(action)) {
|
|
15871
|
+
const verificationCommands = [];
|
|
15872
|
+
if (action === "set_task_verification") {
|
|
15873
|
+
const command = msg.payload?.verificationCommand;
|
|
15874
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192)) return;
|
|
15875
|
+
if (typeof command === "string" && command.trim()) {
|
|
15876
|
+
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
15877
|
+
}
|
|
15878
|
+
} else if (action === "split_task") {
|
|
15879
|
+
const subtasks = msg.payload?.subtasks;
|
|
15880
|
+
if (Array.isArray(subtasks)) {
|
|
15881
|
+
for (const subtask of subtasks) {
|
|
15882
|
+
if (!subtask || typeof subtask !== "object") continue;
|
|
15883
|
+
const criterion = subtask.successCriterion;
|
|
15884
|
+
if (criterion === void 0) continue;
|
|
15885
|
+
if (typeof criterion !== "string") return;
|
|
15886
|
+
const command = extractVerificationCommand([criterion]);
|
|
15887
|
+
if (!command) continue;
|
|
15888
|
+
if (command.length > 8192) return;
|
|
15889
|
+
verificationCommands.push({ command, operation: "sdd.split_task_verification" });
|
|
15890
|
+
}
|
|
15891
|
+
}
|
|
15892
|
+
}
|
|
15893
|
+
for (const { command, operation } of verificationCommands) {
|
|
15894
|
+
if (!this.security) return;
|
|
15895
|
+
const authorization = await authorizeWebUIAction(
|
|
15896
|
+
this.security.trustBoundary,
|
|
15897
|
+
{
|
|
15898
|
+
capability: "process.spawn",
|
|
15899
|
+
subject: { kind: "command", id: command },
|
|
15900
|
+
risk: "high",
|
|
15901
|
+
cwd: this.lifecycle?.projectRoot,
|
|
15902
|
+
metadata: { operation }
|
|
15903
|
+
},
|
|
15904
|
+
this.security.logger
|
|
15905
|
+
);
|
|
15906
|
+
if (!authorization.allowed) return;
|
|
15907
|
+
}
|
|
15648
15908
|
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
15649
15909
|
if (runId) {
|
|
15650
15910
|
await this.store.appendControl(runId, {
|
|
@@ -15757,7 +16017,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15757
16017
|
};
|
|
15758
16018
|
|
|
15759
16019
|
// src/server/sdd-wizard-wiring.ts
|
|
15760
|
-
import * as
|
|
16020
|
+
import * as path17 from "node:path";
|
|
15761
16021
|
import {
|
|
15762
16022
|
DefaultTaskStore,
|
|
15763
16023
|
TaskTracker
|
|
@@ -15871,7 +16131,7 @@ function buildSddWizardDeps(opts) {
|
|
|
15871
16131
|
}).catch(() => {
|
|
15872
16132
|
projectContext = "";
|
|
15873
16133
|
});
|
|
15874
|
-
const sessionPath = opts.paths.projectSddSession ??
|
|
16134
|
+
const sessionPath = opts.paths.projectSddSession ?? path17.join(opts.paths.projectDir, "sdd-session.json");
|
|
15875
16135
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
15876
16136
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
15877
16137
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -16153,7 +16413,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
16153
16413
|
return;
|
|
16154
16414
|
}
|
|
16155
16415
|
const { runId } = await this.deps.startRun(this.driver, opts);
|
|
16156
|
-
this.driver.setLastRunId(runId);
|
|
16416
|
+
await this.driver.setLastRunId(runId);
|
|
16157
16417
|
if (this.driver.phase() !== "executing" && this.driver.phase() !== "done") {
|
|
16158
16418
|
try {
|
|
16159
16419
|
if (this.driver.phase() === "task_review") await this.driver.approve();
|
|
@@ -16212,7 +16472,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
16212
16472
|
this.lastAgentText = text;
|
|
16213
16473
|
if (this.driver) {
|
|
16214
16474
|
await this.driver.ingestAgentOutput(text);
|
|
16215
|
-
this.driver.setLastAgentText(text);
|
|
16475
|
+
await this.driver.setLastAgentText(text);
|
|
16216
16476
|
}
|
|
16217
16477
|
this.broadcast({ type: "sdd.spec.agent_text", payload: { text } });
|
|
16218
16478
|
} finally {
|
|
@@ -16243,10 +16503,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
16243
16503
|
|
|
16244
16504
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
16245
16505
|
import { watch as fsWatch } from "node:fs";
|
|
16246
|
-
import * as
|
|
16506
|
+
import * as path18 from "node:path";
|
|
16247
16507
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
16248
16508
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
16249
|
-
const globalRoot = globalConfigPath ?
|
|
16509
|
+
const globalRoot = globalConfigPath ? path18.dirname(globalConfigPath) : void 0;
|
|
16250
16510
|
if (!globalRoot) return void 0;
|
|
16251
16511
|
const disposers = [];
|
|
16252
16512
|
const broadcastSessions = async () => {
|
|
@@ -16256,8 +16516,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
16256
16516
|
const sessions = await registry.list();
|
|
16257
16517
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
16258
16518
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
16259
|
-
const myRoot =
|
|
16260
|
-
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug :
|
|
16519
|
+
const myRoot = path18.resolve(context.projectRoot);
|
|
16520
|
+
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path18.resolve(s.projectRoot) === myRoot).map((s) => ({
|
|
16261
16521
|
sessionId: s.sessionId,
|
|
16262
16522
|
projectName: s.projectName,
|
|
16263
16523
|
projectSlug: s.projectSlug,
|
|
@@ -16448,13 +16708,13 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
16448
16708
|
// src/server/setup-events-status-watcher.ts
|
|
16449
16709
|
import { watch as fsWatch2 } from "node:fs";
|
|
16450
16710
|
import * as fs16 from "node:fs/promises";
|
|
16451
|
-
import * as
|
|
16711
|
+
import * as path20 from "node:path";
|
|
16452
16712
|
|
|
16453
16713
|
// src/server/setup-events-watcher.ts
|
|
16454
|
-
import * as
|
|
16714
|
+
import * as path19 from "node:path";
|
|
16455
16715
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
16456
16716
|
const raw = String(filename);
|
|
16457
|
-
const relative5 =
|
|
16717
|
+
const relative5 = path19.isAbsolute(raw) ? path19.relative(projectsDir, raw) : raw;
|
|
16458
16718
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
16459
16719
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
16460
16720
|
return parts.at(-2) ?? null;
|
|
@@ -16489,7 +16749,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
16489
16749
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
16490
16750
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
16491
16751
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
16492
|
-
const projectsDir =
|
|
16752
|
+
const projectsDir = path20.join(wpaths.globalRoot, "projects");
|
|
16493
16753
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
16494
16754
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
16495
16755
|
const DEBOUNCE_MS = 150;
|
|
@@ -16548,7 +16808,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16548
16808
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
16549
16809
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
16550
16810
|
try {
|
|
16551
|
-
const targetFile =
|
|
16811
|
+
const targetFile = path20.join(projectsDir, projectHash, "status.json");
|
|
16552
16812
|
const content = await fs16.readFile(targetFile, "utf-8");
|
|
16553
16813
|
const statusData = JSON.parse(content);
|
|
16554
16814
|
scheduleBroadcast(projectHash, statusData);
|
|
@@ -16607,7 +16867,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16607
16867
|
|
|
16608
16868
|
// src/server/setup-events-core-watchers.ts
|
|
16609
16869
|
import * as fs17 from "node:fs/promises";
|
|
16610
|
-
import * as
|
|
16870
|
+
import * as path21 from "node:path";
|
|
16611
16871
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
16612
16872
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
16613
16873
|
const disposers = [];
|
|
@@ -16643,7 +16903,7 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
16643
16903
|
if (wpaths?.projectStatus) {
|
|
16644
16904
|
try {
|
|
16645
16905
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
16646
|
-
const dir =
|
|
16906
|
+
const dir = path21.dirname(statusFile);
|
|
16647
16907
|
await fs17.mkdir(dir, { recursive: true });
|
|
16648
16908
|
await fs17.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
16649
16909
|
} catch (err) {
|
|
@@ -16833,6 +17093,9 @@ function setupEvents(deps2) {
|
|
|
16833
17093
|
input: scrub(e.input),
|
|
16834
17094
|
fileTargets: extractCodeMapFileTargets(projectRoot || ".", e.name, e.input),
|
|
16835
17095
|
output: scrub(e.output),
|
|
17096
|
+
// SAGE-injected memory rides beside the tool text so the client renders
|
|
17097
|
+
// it as a memory card. Never folded back into `output`.
|
|
17098
|
+
...e.sage && e.sage.length > 0 ? { sage: e.sage.map((line) => scrub(line)) } : {},
|
|
16836
17099
|
outputBytes: e.outputBytes,
|
|
16837
17100
|
outputTokens: e.outputTokens,
|
|
16838
17101
|
outputLines: e.outputLines,
|
|
@@ -17667,17 +17930,24 @@ var SpecsWebSocketHandler = class {
|
|
|
17667
17930
|
// src/server/start-webui.ts
|
|
17668
17931
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17669
17932
|
import * as http2 from "node:http";
|
|
17670
|
-
import * as
|
|
17933
|
+
import * as path28 from "node:path";
|
|
17671
17934
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
17672
17935
|
import { getSharedProjectMailbox as getSharedProjectMailbox4, resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
|
|
17673
17936
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
17674
17937
|
import {
|
|
17938
|
+
attachTodosCheckpoint,
|
|
17675
17939
|
createSessionEventBridge,
|
|
17676
17940
|
resolveSessionLoggingConfig,
|
|
17677
17941
|
watchProviderConfig
|
|
17678
17942
|
} from "@wrongstack/core/storage";
|
|
17679
17943
|
import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } from "@wrongstack/core/types";
|
|
17680
|
-
import {
|
|
17944
|
+
import {
|
|
17945
|
+
expectDefined as expectDefined3,
|
|
17946
|
+
sessionScopedPath as sessionScopedPath3,
|
|
17947
|
+
startHeapWatchdog,
|
|
17948
|
+
toErrorMessage as toErrorMessage13,
|
|
17949
|
+
wstackGlobalRoot as wstackGlobalRoot2
|
|
17950
|
+
} from "@wrongstack/core/utils";
|
|
17681
17951
|
import { makeProviderFromConfig as makeProviderFromConfig3 } from "@wrongstack/providers";
|
|
17682
17952
|
import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
17683
17953
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
@@ -17864,7 +18134,7 @@ function findWorkspaceCliEntry(projectRoot) {
|
|
|
17864
18134
|
return null;
|
|
17865
18135
|
}
|
|
17866
18136
|
function sleep(ms) {
|
|
17867
|
-
return new Promise((
|
|
18137
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
17868
18138
|
}
|
|
17869
18139
|
|
|
17870
18140
|
// src/server/terminal-ws-handler.ts
|
|
@@ -18100,7 +18370,7 @@ function clampDim(value, fallback) {
|
|
|
18100
18370
|
}
|
|
18101
18371
|
|
|
18102
18372
|
// src/server/worktree-ws-handler.ts
|
|
18103
|
-
import { join as join11, resolve as
|
|
18373
|
+
import { join as join11, resolve as resolve12, sep as sep5 } from "node:path";
|
|
18104
18374
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
18105
18375
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
18106
18376
|
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
@@ -18161,13 +18431,13 @@ var WorktreeWebSocketHandler = class {
|
|
|
18161
18431
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
18162
18432
|
/** Absolute managed-worktrees root for this project. */
|
|
18163
18433
|
worktreesRoot() {
|
|
18164
|
-
return
|
|
18434
|
+
return resolve12(join11(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
18165
18435
|
}
|
|
18166
18436
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
18167
18437
|
underRoot(dir) {
|
|
18168
|
-
const abs =
|
|
18438
|
+
const abs = resolve12(dir);
|
|
18169
18439
|
const root = this.worktreesRoot();
|
|
18170
|
-
return abs !== root && abs.startsWith(root +
|
|
18440
|
+
return abs !== root && abs.startsWith(root + sep5);
|
|
18171
18441
|
}
|
|
18172
18442
|
/** Branches of worktrees a live in-session run currently owns. */
|
|
18173
18443
|
liveActiveBranches() {
|
|
@@ -18315,7 +18585,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
18315
18585
|
}
|
|
18316
18586
|
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
18317
18587
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
18318
|
-
const summary = await wt.diffSummary(
|
|
18588
|
+
const summary = await wt.diffSummary(resolve12(dir), base);
|
|
18319
18589
|
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
18320
18590
|
}
|
|
18321
18591
|
// ── internals ───────────────────────────────────────────────────────────
|
|
@@ -18464,7 +18734,9 @@ async function createAgentServices(input) {
|
|
|
18464
18734
|
memory: memoryRetrieval,
|
|
18465
18735
|
maxHintsPerTool: config.Sage?.inject?.maxHintsPerTool,
|
|
18466
18736
|
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
18737
|
+
taskAware: config.Sage?.inject?.taskAware,
|
|
18467
18738
|
minScore: config.Sage?.inject?.minScore,
|
|
18739
|
+
minImportance: config.Sage?.inject?.minImportance,
|
|
18468
18740
|
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
18469
18741
|
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
18470
18742
|
triggers: config.Sage?.inject?.triggers
|
|
@@ -18767,15 +19039,20 @@ async function createAgentServices(input) {
|
|
|
18767
19039
|
projectRoot
|
|
18768
19040
|
);
|
|
18769
19041
|
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
18770
|
-
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
19042
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
19043
|
+
wpaths.projectSddBoards,
|
|
19044
|
+
void 0,
|
|
19045
|
+
{
|
|
19046
|
+
projectRoot,
|
|
19047
|
+
paths: {
|
|
19048
|
+
projectSpecs: wpaths.projectSpecs,
|
|
19049
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
19050
|
+
projectSddSession: wpaths.projectSddSession,
|
|
19051
|
+
projectSddBoards: wpaths.projectSddBoards
|
|
19052
|
+
}
|
|
19053
|
+
},
|
|
19054
|
+
{ trustBoundary: input.trustBoundary, logger }
|
|
19055
|
+
);
|
|
18779
19056
|
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
18780
19057
|
buildSddWizardDeps({
|
|
18781
19058
|
agent,
|
|
@@ -18787,7 +19064,16 @@ async function createAgentServices(input) {
|
|
|
18787
19064
|
providerRegistry,
|
|
18788
19065
|
toolRegistry,
|
|
18789
19066
|
session: input.sessionGetter(),
|
|
18790
|
-
projectRoot
|
|
19067
|
+
projectRoot,
|
|
19068
|
+
// Thread the container-provided ProviderModelStatusTracker so a 429
|
|
19069
|
+
// from this subagent's first call transitions the (provider, model)
|
|
19070
|
+
// pair to `state: 'blocked'` instead of silently no-op'ing. The
|
|
19071
|
+
// runtime container binds a default `ProviderModelStatusTracker`
|
|
19072
|
+
// (see packages/runtime/src/container.ts); without this dep, the
|
|
19073
|
+
// subagent's fallback extension's tracker hooks are undefined and
|
|
19074
|
+
// round-robin keeps reassigning the doomed model. Mirrors the CLI
|
|
19075
|
+
// factory wiring at host-subagent-factory.ts:337.
|
|
19076
|
+
statusTracker: container.safeResolve(TOKENS.ProviderModelStatusTracker)
|
|
18791
19077
|
}),
|
|
18792
19078
|
paths: {
|
|
18793
19079
|
projectSpecs: wpaths.projectSpecs,
|
|
@@ -18930,7 +19216,7 @@ function createConnectionHandler(options) {
|
|
|
18930
19216
|
}
|
|
18931
19217
|
|
|
18932
19218
|
// src/server/message-dispatcher.ts
|
|
18933
|
-
import
|
|
19219
|
+
import path22 from "node:path";
|
|
18934
19220
|
function createMessageDispatcher(opts) {
|
|
18935
19221
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
18936
19222
|
function makeWorklistContext() {
|
|
@@ -18951,7 +19237,7 @@ function createMessageDispatcher(opts) {
|
|
|
18951
19237
|
skillLoader: deps2.skillLoader,
|
|
18952
19238
|
skillInstaller: deps2.skillInstaller,
|
|
18953
19239
|
projectRoot,
|
|
18954
|
-
projectSkillsDir:
|
|
19240
|
+
projectSkillsDir: path22.join(projectRoot, ".wrongstack", "skills"),
|
|
18955
19241
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
18956
19242
|
};
|
|
18957
19243
|
}
|
|
@@ -19203,7 +19489,7 @@ function createMessageDispatcher(opts) {
|
|
|
19203
19489
|
|
|
19204
19490
|
// src/server/pre-context-services.ts
|
|
19205
19491
|
import { createRequire as createRequire3 } from "node:module";
|
|
19206
|
-
import * as
|
|
19492
|
+
import * as path25 from "node:path";
|
|
19207
19493
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
19208
19494
|
import {
|
|
19209
19495
|
getSharedProjectMailbox as getSharedProjectMailbox3,
|
|
@@ -19258,7 +19544,7 @@ import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/too
|
|
|
19258
19544
|
|
|
19259
19545
|
// src/server/model-auto-discovery.ts
|
|
19260
19546
|
import * as fs18 from "node:fs/promises";
|
|
19261
|
-
import * as
|
|
19547
|
+
import * as path23 from "node:path";
|
|
19262
19548
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
19263
19549
|
function isOverlayRegistry(value) {
|
|
19264
19550
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -19294,7 +19580,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19294
19580
|
if (!isOverlayRegistry(registry)) return;
|
|
19295
19581
|
const targets = eligibleProviders(opts.config);
|
|
19296
19582
|
if (targets.length === 0) return;
|
|
19297
|
-
const cacheFile =
|
|
19583
|
+
const cacheFile = path23.join(opts.cacheDir, "discovered-models-cache.json");
|
|
19298
19584
|
const cache2 = await readCache(cacheFile);
|
|
19299
19585
|
let cacheDirty = false;
|
|
19300
19586
|
await Promise.all(
|
|
@@ -19331,7 +19617,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19331
19617
|
);
|
|
19332
19618
|
if (cacheDirty) {
|
|
19333
19619
|
try {
|
|
19334
|
-
await fs18.mkdir(
|
|
19620
|
+
await fs18.mkdir(path23.dirname(cacheFile), { recursive: true });
|
|
19335
19621
|
await fs18.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
19336
19622
|
} catch {
|
|
19337
19623
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
@@ -19428,7 +19714,7 @@ function resolveSetupProvider(opts) {
|
|
|
19428
19714
|
}
|
|
19429
19715
|
|
|
19430
19716
|
// src/server/standalone-session-identity.ts
|
|
19431
|
-
import * as
|
|
19717
|
+
import * as path24 from "node:path";
|
|
19432
19718
|
import {
|
|
19433
19719
|
AgentStatusTracker,
|
|
19434
19720
|
FleetNotifier,
|
|
@@ -19447,7 +19733,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
19447
19733
|
let activeTarget = {
|
|
19448
19734
|
projectSlug: paths.projectSlug,
|
|
19449
19735
|
projectRoot: paths.projectRoot,
|
|
19450
|
-
projectName:
|
|
19736
|
+
projectName: path24.basename(paths.projectRoot),
|
|
19451
19737
|
workingDir: opts.workingDir
|
|
19452
19738
|
};
|
|
19453
19739
|
let pendingClaim;
|
|
@@ -19678,7 +19964,7 @@ async function createPreContextServices(input) {
|
|
|
19678
19964
|
await discoverAndMergeWebuiProviders({
|
|
19679
19965
|
config,
|
|
19680
19966
|
registry: modelsRegistry,
|
|
19681
|
-
cacheDir:
|
|
19967
|
+
cacheDir: path25.dirname(wpaths.modelsCache),
|
|
19682
19968
|
logger
|
|
19683
19969
|
});
|
|
19684
19970
|
} catch (err) {
|
|
@@ -19730,7 +20016,7 @@ async function createPreContextServices(input) {
|
|
|
19730
20016
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
19731
20017
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
19732
20018
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
19733
|
-
|
|
20019
|
+
path25.join(wpaths.projectDir, "mcp-auth.json"),
|
|
19734
20020
|
vault
|
|
19735
20021
|
);
|
|
19736
20022
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -19836,7 +20122,7 @@ async function createPreContextServices(input) {
|
|
|
19836
20122
|
};
|
|
19837
20123
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
19838
20124
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
19839
|
-
manifestPath:
|
|
20125
|
+
manifestPath: path25.join(wpaths.configDir, "installed-skills.json"),
|
|
19840
20126
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
19841
20127
|
globalSkillsDir: wpaths.globalSkills,
|
|
19842
20128
|
projectHash: wpaths.projectHash,
|
|
@@ -19846,8 +20132,8 @@ async function createPreContextServices(input) {
|
|
|
19846
20132
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
19847
20133
|
try {
|
|
19848
20134
|
const req = createRequire3(import.meta.url);
|
|
19849
|
-
return
|
|
19850
|
-
|
|
20135
|
+
return path25.join(
|
|
20136
|
+
path25.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
19851
20137
|
"data",
|
|
19852
20138
|
"prompts"
|
|
19853
20139
|
);
|
|
@@ -19951,7 +20237,7 @@ async function createPreContextServices(input) {
|
|
|
19951
20237
|
}
|
|
19952
20238
|
|
|
19953
20239
|
// src/server/routes.ts
|
|
19954
|
-
import
|
|
20240
|
+
import path26 from "node:path";
|
|
19955
20241
|
import { makeProviderFromConfig as makeProviderFromConfig2, withCatalogCapabilities } from "@wrongstack/providers";
|
|
19956
20242
|
|
|
19957
20243
|
// src/server/mode-handlers.ts
|
|
@@ -20076,6 +20362,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20076
20362
|
config: state.getConfig(),
|
|
20077
20363
|
clients: state.getClients(),
|
|
20078
20364
|
context: deps2.context,
|
|
20365
|
+
events: deps2.events,
|
|
20079
20366
|
toolRegistry: deps2.toolRegistry,
|
|
20080
20367
|
compactor: deps2.compactor,
|
|
20081
20368
|
customModeStore: deps2.customModeStore,
|
|
@@ -20087,6 +20374,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20087
20374
|
setSession: state.setSession,
|
|
20088
20375
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
20089
20376
|
claimSession: cb.claimSession,
|
|
20377
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
20090
20378
|
onSessionSwapped: cb.onSessionSwapped,
|
|
20091
20379
|
abortActiveRun: state.abortRunLock,
|
|
20092
20380
|
isRunActive: state.isRunActive,
|
|
@@ -20108,6 +20396,8 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20108
20396
|
setSessionStore: state.setSessionStore,
|
|
20109
20397
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
20110
20398
|
abortRunLock: state.abortRunLock,
|
|
20399
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
20400
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
20111
20401
|
sessionStartPayload: cb.sessionStartPayload
|
|
20112
20402
|
});
|
|
20113
20403
|
const modeRoutes = createModeHandlers({
|
|
@@ -20239,7 +20529,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20239
20529
|
};
|
|
20240
20530
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
20241
20531
|
getProjectRoot: state.getProjectRoot,
|
|
20242
|
-
getGlobalRoot: () =>
|
|
20532
|
+
getGlobalRoot: () => path26.dirname(deps2.globalConfigPath),
|
|
20243
20533
|
events: deps2.events
|
|
20244
20534
|
});
|
|
20245
20535
|
const mcpRoutes = {
|
|
@@ -20312,7 +20602,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20312
20602
|
}
|
|
20313
20603
|
|
|
20314
20604
|
// src/server/server-runtime.ts
|
|
20315
|
-
import * as
|
|
20605
|
+
import * as path27 from "node:path";
|
|
20316
20606
|
import { createRequire as createRequire4 } from "node:module";
|
|
20317
20607
|
import { fileURLToPath } from "node:url";
|
|
20318
20608
|
import { WebSocketServer } from "ws";
|
|
@@ -20373,7 +20663,7 @@ function createSessionStartPayload(g) {
|
|
|
20373
20663
|
inputCost,
|
|
20374
20664
|
outputCost,
|
|
20375
20665
|
cacheReadCost,
|
|
20376
|
-
projectName:
|
|
20666
|
+
projectName: path27.basename(projectRoot) || projectRoot,
|
|
20377
20667
|
projectRoot,
|
|
20378
20668
|
cwd: g.getWorkingDir(),
|
|
20379
20669
|
mode: g.getModeId(),
|
|
@@ -20461,13 +20751,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
20461
20751
|
};
|
|
20462
20752
|
}
|
|
20463
20753
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
20464
|
-
if (explicitDistDir) return
|
|
20754
|
+
if (explicitDistDir) return path27.resolve(explicitDistDir);
|
|
20465
20755
|
try {
|
|
20466
20756
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
20467
20757
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
20468
|
-
return
|
|
20758
|
+
return path27.dirname(serverEntry);
|
|
20469
20759
|
} catch {
|
|
20470
|
-
return
|
|
20760
|
+
return path27.resolve(path27.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
20471
20761
|
}
|
|
20472
20762
|
}
|
|
20473
20763
|
function startHttpServer(opts) {
|
|
@@ -20498,6 +20788,56 @@ function registerShutdown(deps2) {
|
|
|
20498
20788
|
}
|
|
20499
20789
|
|
|
20500
20790
|
// src/server/start-webui.ts
|
|
20791
|
+
function createStandaloneTodosCheckpointLifecycle(input) {
|
|
20792
|
+
let checkpointSessionId = input.sessionId;
|
|
20793
|
+
let checkpointSessionsDir = input.sessionsDir;
|
|
20794
|
+
const attachCheckpoint = (sessionId, sessionsDir) => attachTodosCheckpoint(
|
|
20795
|
+
input.state,
|
|
20796
|
+
sessionScopedPath3(sessionsDir, sessionId, ".todos.json"),
|
|
20797
|
+
sessionId,
|
|
20798
|
+
input.events,
|
|
20799
|
+
input.traceId,
|
|
20800
|
+
input.warn
|
|
20801
|
+
);
|
|
20802
|
+
let detachCurrent = attachCheckpoint(input.sessionId, input.sessionsDir);
|
|
20803
|
+
let checkpointAttached = true;
|
|
20804
|
+
const detachCurrentCheckpoint = async () => {
|
|
20805
|
+
if (!checkpointAttached) return;
|
|
20806
|
+
checkpointAttached = false;
|
|
20807
|
+
await detachCurrent();
|
|
20808
|
+
};
|
|
20809
|
+
let transitionTail = Promise.resolve();
|
|
20810
|
+
const rebind = (nextSessionId, sessionsDir) => {
|
|
20811
|
+
const transition = transitionTail.then(async () => {
|
|
20812
|
+
if (checkpointAttached && nextSessionId === checkpointSessionId && sessionsDir === checkpointSessionsDir) {
|
|
20813
|
+
return;
|
|
20814
|
+
}
|
|
20815
|
+
let detachFailed = false;
|
|
20816
|
+
let detachError;
|
|
20817
|
+
try {
|
|
20818
|
+
await detachCurrentCheckpoint();
|
|
20819
|
+
} catch (error2) {
|
|
20820
|
+
detachFailed = true;
|
|
20821
|
+
detachError = error2;
|
|
20822
|
+
}
|
|
20823
|
+
const nextDetach = attachCheckpoint(nextSessionId, sessionsDir);
|
|
20824
|
+
checkpointSessionId = nextSessionId;
|
|
20825
|
+
checkpointSessionsDir = sessionsDir;
|
|
20826
|
+
detachCurrent = nextDetach;
|
|
20827
|
+
checkpointAttached = true;
|
|
20828
|
+
if (detachFailed) throw detachError;
|
|
20829
|
+
});
|
|
20830
|
+
transitionTail = transition.catch(() => void 0);
|
|
20831
|
+
return transition;
|
|
20832
|
+
};
|
|
20833
|
+
return {
|
|
20834
|
+
rebind,
|
|
20835
|
+
detach: async () => {
|
|
20836
|
+
await transitionTail;
|
|
20837
|
+
await detachCurrentCheckpoint();
|
|
20838
|
+
}
|
|
20839
|
+
};
|
|
20840
|
+
}
|
|
20501
20841
|
async function startWebUI(opts = {}) {
|
|
20502
20842
|
ensureSessionShell();
|
|
20503
20843
|
const ports = await resolvePorts(opts);
|
|
@@ -20565,6 +20905,14 @@ async function startWebUI(opts = {}) {
|
|
|
20565
20905
|
} = preContext;
|
|
20566
20906
|
let sessionStore = preContext.sessionStore;
|
|
20567
20907
|
let session = preContext.session;
|
|
20908
|
+
const todosCheckpoint = createStandaloneTodosCheckpointLifecycle({
|
|
20909
|
+
state: context.state,
|
|
20910
|
+
sessionsDir: wpaths.projectSessions,
|
|
20911
|
+
sessionId: session.id,
|
|
20912
|
+
events,
|
|
20913
|
+
traceId: context.traceId,
|
|
20914
|
+
warn: (message) => logger.warn(message)
|
|
20915
|
+
});
|
|
20568
20916
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
20569
20917
|
let modeId = preContext.modeId;
|
|
20570
20918
|
const needsSetup = preContext.needsSetup;
|
|
@@ -20691,7 +21039,7 @@ async function startWebUI(opts = {}) {
|
|
|
20691
21039
|
if (events.listenerCount("tool.confirm_needed") === 0) {
|
|
20692
21040
|
throw new Error("No permission confirmation surface is connected");
|
|
20693
21041
|
}
|
|
20694
|
-
const decision = await new Promise((
|
|
21042
|
+
const decision = await new Promise((resolve15) => {
|
|
20695
21043
|
events.emit("tool.confirm_needed", {
|
|
20696
21044
|
sessionId: context.session.id,
|
|
20697
21045
|
tool: confirmTool,
|
|
@@ -20701,7 +21049,7 @@ async function startWebUI(opts = {}) {
|
|
|
20701
21049
|
decisionSource: pending.decisionSource,
|
|
20702
21050
|
riskTier: pending.riskTier,
|
|
20703
21051
|
boundaryReason: pending.boundaryReason,
|
|
20704
|
-
resolve:
|
|
21052
|
+
resolve: resolve15
|
|
20705
21053
|
});
|
|
20706
21054
|
});
|
|
20707
21055
|
const rule = { tool: "language_package", pattern: pending.suggestedPattern };
|
|
@@ -20801,21 +21149,21 @@ async function startWebUI(opts = {}) {
|
|
|
20801
21149
|
});
|
|
20802
21150
|
}
|
|
20803
21151
|
async function touchProjectEntry(root, workDir) {
|
|
20804
|
-
const resolved =
|
|
21152
|
+
const resolved = path28.resolve(root);
|
|
20805
21153
|
const manifest = await loadManifest(globalConfigPath);
|
|
20806
21154
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20807
|
-
const existing = manifest.projects.find((p) =>
|
|
21155
|
+
const existing = manifest.projects.find((p) => path28.resolve(p.root) === resolved);
|
|
20808
21156
|
if (existing) {
|
|
20809
21157
|
existing.lastSeen = now;
|
|
20810
|
-
if (workDir) existing.lastWorkingDir =
|
|
21158
|
+
if (workDir) existing.lastWorkingDir = path28.resolve(workDir);
|
|
20811
21159
|
} else {
|
|
20812
21160
|
manifest.projects.push({
|
|
20813
|
-
name:
|
|
21161
|
+
name: path28.basename(resolved),
|
|
20814
21162
|
root: resolved,
|
|
20815
21163
|
slug: generateProjectSlug(resolved),
|
|
20816
21164
|
createdAt: now,
|
|
20817
21165
|
lastSeen: now,
|
|
20818
|
-
lastWorkingDir: workDir ?
|
|
21166
|
+
lastWorkingDir: workDir ? path28.resolve(workDir) : void 0
|
|
20819
21167
|
});
|
|
20820
21168
|
}
|
|
20821
21169
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -20916,6 +21264,7 @@ async function startWebUI(opts = {}) {
|
|
|
20916
21264
|
const cb = {
|
|
20917
21265
|
sessionStartPayload,
|
|
20918
21266
|
claimSession: (sessionId, target) => sessionIdentity.claim(sessionId, target),
|
|
21267
|
+
onBeforeSessionTodosReplaced: todosCheckpoint.rebind,
|
|
20919
21268
|
onSessionSwapped: async (sessionId, target) => {
|
|
20920
21269
|
await sessionIdentity.activate(sessionId, target);
|
|
20921
21270
|
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
@@ -21066,6 +21415,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21066
21415
|
...wssSecondary ? [wssSecondary] : []
|
|
21067
21416
|
],
|
|
21068
21417
|
onShutdown: async () => {
|
|
21418
|
+
await todosCheckpoint.detach();
|
|
21069
21419
|
await stopHeapWatchdog();
|
|
21070
21420
|
credentialWatcherClose?.();
|
|
21071
21421
|
brainMonitor.stop();
|
|
@@ -21091,7 +21441,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21091
21441
|
await memoryStore.dispose().catch(
|
|
21092
21442
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage13(err)}`)
|
|
21093
21443
|
);
|
|
21094
|
-
await unregisterInstance(process.pid,
|
|
21444
|
+
await unregisterInstance(process.pid, path28.dirname(globalConfigPath));
|
|
21095
21445
|
}
|
|
21096
21446
|
});
|
|
21097
21447
|
}
|