@wrongstack/webui-server 0.296.2 → 0.296.4
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 +851 -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 +573 -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,12 @@ 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
|
+
resolveProjectIndexDaemonAvailability,
|
|
4875
|
+
shutdownCodebaseIndexServer
|
|
4876
|
+
} from "@wrongstack/tools";
|
|
4868
4877
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
4869
4878
|
if (message.type !== "connections.health") return false;
|
|
4870
4879
|
try {
|
|
@@ -4955,6 +4964,19 @@ async function chronicleHealth(projectRoot) {
|
|
|
4955
4964
|
}
|
|
4956
4965
|
async function codebaseIndexHealth(projectRoot, indexDir) {
|
|
4957
4966
|
const startedAt = Date.now();
|
|
4967
|
+
const availability = resolveProjectIndexDaemonAvailability(projectRoot, indexDir);
|
|
4968
|
+
if (availability.kind === "endpoint-invalid") {
|
|
4969
|
+
return {
|
|
4970
|
+
id: "codebase-index",
|
|
4971
|
+
label: "Codebase index",
|
|
4972
|
+
status: "unavailable",
|
|
4973
|
+
required: false,
|
|
4974
|
+
mode: "endpoint-invalid",
|
|
4975
|
+
detail: `Socket path is ${availability.byteLength} bytes \u2014 over this platform's ${availability.maxBytes}-byte sun_path limit. Queries fall back to a process-local index. Set a shorter TMPDIR to restore the shared daemon.`,
|
|
4976
|
+
endpoint: availability.endpoint,
|
|
4977
|
+
latencyMs: Date.now() - startedAt
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4958
4980
|
try {
|
|
4959
4981
|
const health = await checkCodebaseIndexServerHealth(projectRoot, indexDir, {
|
|
4960
4982
|
timeoutMs: 2e3
|
|
@@ -5341,9 +5363,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5341
5363
|
const cwd = projectRoot || void 0;
|
|
5342
5364
|
try {
|
|
5343
5365
|
const { execFile: ef } = await import("node:child_process");
|
|
5344
|
-
const git = (args) => new Promise((
|
|
5366
|
+
const git = (args) => new Promise((resolve15) => {
|
|
5345
5367
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
5346
|
-
|
|
5368
|
+
resolve15(err ? "" : stdout.trim());
|
|
5347
5369
|
});
|
|
5348
5370
|
});
|
|
5349
5371
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -5369,12 +5391,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5369
5391
|
function makeGit(cwd) {
|
|
5370
5392
|
return async (args) => {
|
|
5371
5393
|
const { execFile: ef } = await import("node:child_process");
|
|
5372
|
-
return new Promise((
|
|
5394
|
+
return new Promise((resolve15) => {
|
|
5373
5395
|
ef(
|
|
5374
5396
|
"git",
|
|
5375
5397
|
args,
|
|
5376
5398
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
5377
|
-
(err, stdout) =>
|
|
5399
|
+
(err, stdout) => resolve15(err ? "" : stdout)
|
|
5378
5400
|
);
|
|
5379
5401
|
});
|
|
5380
5402
|
};
|
|
@@ -5398,15 +5420,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5398
5420
|
if (!m) continue;
|
|
5399
5421
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5400
5422
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5401
|
-
let
|
|
5402
|
-
if (
|
|
5423
|
+
let path29 = m[3] ?? "";
|
|
5424
|
+
if (path29 === "") {
|
|
5403
5425
|
i += 1;
|
|
5404
|
-
|
|
5426
|
+
path29 = parts[i + 1] ?? parts[i] ?? "";
|
|
5405
5427
|
i += 1;
|
|
5406
5428
|
}
|
|
5407
|
-
if (!
|
|
5408
|
-
const prev = counts.get(
|
|
5409
|
-
counts.set(
|
|
5429
|
+
if (!path29) continue;
|
|
5430
|
+
const prev = counts.get(path29) ?? { added: 0, deleted: 0 };
|
|
5431
|
+
counts.set(path29, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5410
5432
|
}
|
|
5411
5433
|
};
|
|
5412
5434
|
parseNumstat(unstagedNumstat);
|
|
@@ -5418,7 +5440,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5418
5440
|
if (!rec || rec.length < 3) continue;
|
|
5419
5441
|
const x = rec[0] ?? " ";
|
|
5420
5442
|
const y = rec[1] ?? " ";
|
|
5421
|
-
const
|
|
5443
|
+
const path29 = rec.slice(3);
|
|
5422
5444
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5423
5445
|
if (isRename) i += 1;
|
|
5424
5446
|
let status;
|
|
@@ -5430,13 +5452,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5430
5452
|
else if (x === "D" || y === "D") status = "D";
|
|
5431
5453
|
else status = "M";
|
|
5432
5454
|
const staged = x !== " " && x !== "?";
|
|
5433
|
-
let added = counts.get(
|
|
5434
|
-
let deleted = counts.get(
|
|
5455
|
+
let added = counts.get(path29)?.added ?? 0;
|
|
5456
|
+
let deleted = counts.get(path29)?.deleted ?? 0;
|
|
5435
5457
|
if (status === "?") {
|
|
5436
5458
|
added = 0;
|
|
5437
5459
|
deleted = 0;
|
|
5438
5460
|
}
|
|
5439
|
-
files.push({ path:
|
|
5461
|
+
files.push({ path: path29, status, added, deleted, staged });
|
|
5440
5462
|
}
|
|
5441
5463
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5442
5464
|
} catch (err) {
|
|
@@ -5447,10 +5469,10 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5447
5469
|
}
|
|
5448
5470
|
}
|
|
5449
5471
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5450
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5472
|
+
async function handleGitDiff(ws, projectRoot, path29) {
|
|
5451
5473
|
const cwd = projectRoot || void 0;
|
|
5452
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5453
|
-
if (!
|
|
5474
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path29, ...extra } });
|
|
5475
|
+
if (!path29 || path29.includes("\0") || path29.includes("..") || nodePath.isAbsolute(path29)) {
|
|
5454
5476
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5455
5477
|
return;
|
|
5456
5478
|
}
|
|
@@ -5458,10 +5480,10 @@ async function handleGitDiff(ws, projectRoot, path28) {
|
|
|
5458
5480
|
const git = makeGit(cwd);
|
|
5459
5481
|
const { readFile: readFile11 } = await import("node:fs/promises");
|
|
5460
5482
|
const { join: join15 } = await import("node:path");
|
|
5461
|
-
const oldText = await git(["show", `HEAD:${
|
|
5483
|
+
const oldText = await git(["show", `HEAD:${path29}`]);
|
|
5462
5484
|
let newText = "";
|
|
5463
5485
|
try {
|
|
5464
|
-
const abs = cwd ? join15(cwd,
|
|
5486
|
+
const abs = cwd ? join15(cwd, path29) : path29;
|
|
5465
5487
|
const buf = await readFile11(abs);
|
|
5466
5488
|
if (buf.includes(0)) {
|
|
5467
5489
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5541,7 +5563,7 @@ import { execFile } from "node:child_process";
|
|
|
5541
5563
|
var GIT_TIMEOUT_MS = 1e4;
|
|
5542
5564
|
var GIT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
5543
5565
|
function gitStdout(cwd, args) {
|
|
5544
|
-
return new Promise((
|
|
5566
|
+
return new Promise((resolve15) => {
|
|
5545
5567
|
execFile(
|
|
5546
5568
|
"git",
|
|
5547
5569
|
[...args],
|
|
@@ -5552,7 +5574,7 @@ function gitStdout(cwd, args) {
|
|
|
5552
5574
|
timeout: GIT_TIMEOUT_MS,
|
|
5553
5575
|
maxBuffer: GIT_MAX_OUTPUT_BYTES
|
|
5554
5576
|
},
|
|
5555
|
-
(error2, stdout) =>
|
|
5577
|
+
(error2, stdout) => resolve15(error2 ? null : stdout)
|
|
5556
5578
|
);
|
|
5557
5579
|
});
|
|
5558
5580
|
}
|
|
@@ -5818,13 +5840,13 @@ var GoalWebSocketHandler = class {
|
|
|
5818
5840
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
5819
5841
|
try {
|
|
5820
5842
|
const { exec } = await import("node:child_process");
|
|
5821
|
-
const result = await new Promise((
|
|
5843
|
+
const result = await new Promise((resolve15) => {
|
|
5822
5844
|
exec("npx tsc --noEmit", { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
5823
5845
|
if (err && err.code === "ENOENT") {
|
|
5824
|
-
|
|
5846
|
+
resolve15("[verify] tsc not found \u2014 skipping");
|
|
5825
5847
|
return;
|
|
5826
5848
|
}
|
|
5827
|
-
|
|
5849
|
+
resolve15(stdout + stderr);
|
|
5828
5850
|
});
|
|
5829
5851
|
});
|
|
5830
5852
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
@@ -6503,7 +6525,7 @@ function pushEvent(event) {
|
|
|
6503
6525
|
}
|
|
6504
6526
|
}
|
|
6505
6527
|
function parseBody(req) {
|
|
6506
|
-
return new Promise((
|
|
6528
|
+
return new Promise((resolve15, reject) => {
|
|
6507
6529
|
let body = "";
|
|
6508
6530
|
let bodyBytes = 0;
|
|
6509
6531
|
let tooLarge = false;
|
|
@@ -6523,7 +6545,7 @@ function parseBody(req) {
|
|
|
6523
6545
|
return;
|
|
6524
6546
|
}
|
|
6525
6547
|
try {
|
|
6526
|
-
|
|
6548
|
+
resolve15(JSON.parse(body));
|
|
6527
6549
|
} catch {
|
|
6528
6550
|
reject(new Error("Invalid JSON"));
|
|
6529
6551
|
}
|
|
@@ -6600,7 +6622,7 @@ async function handleApiAnalyticsSummary(res) {
|
|
|
6600
6622
|
// src/server/http-server.ts
|
|
6601
6623
|
import * as fs9 from "node:fs/promises";
|
|
6602
6624
|
import * as http from "node:http";
|
|
6603
|
-
import * as
|
|
6625
|
+
import * as path12 from "node:path";
|
|
6604
6626
|
import * as v8 from "node:v8";
|
|
6605
6627
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6606
6628
|
|
|
@@ -6680,6 +6702,156 @@ async function handleCodemapSymbols(res, deps2, file) {
|
|
|
6680
6702
|
);
|
|
6681
6703
|
}
|
|
6682
6704
|
|
|
6705
|
+
// src/server/deadcode-handlers.ts
|
|
6706
|
+
import * as path10 from "node:path";
|
|
6707
|
+
import { runDeadCodeScan } from "@wrongstack/tools/codebase-index";
|
|
6708
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
6709
|
+
function readJsonBody(req) {
|
|
6710
|
+
return new Promise((resolve15, reject) => {
|
|
6711
|
+
const chunks = [];
|
|
6712
|
+
let total = 0;
|
|
6713
|
+
req.on("data", (chunk) => {
|
|
6714
|
+
total += chunk.length;
|
|
6715
|
+
if (total > MAX_BODY_BYTES) {
|
|
6716
|
+
req.destroy(new Error("Request body too large"));
|
|
6717
|
+
reject(new Error("Request body exceeds 10 MiB limit"));
|
|
6718
|
+
return;
|
|
6719
|
+
}
|
|
6720
|
+
chunks.push(chunk);
|
|
6721
|
+
});
|
|
6722
|
+
req.on("end", () => resolve15(Buffer.concat(chunks).toString("utf8")));
|
|
6723
|
+
req.on("error", (err) => reject(err));
|
|
6724
|
+
});
|
|
6725
|
+
}
|
|
6726
|
+
async function handleDeadCodeScan(res, deps2, req) {
|
|
6727
|
+
try {
|
|
6728
|
+
let body = {};
|
|
6729
|
+
const raw = await readJsonBody(req);
|
|
6730
|
+
if (raw) {
|
|
6731
|
+
try {
|
|
6732
|
+
body = JSON.parse(raw);
|
|
6733
|
+
} catch {
|
|
6734
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6735
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
6736
|
+
return;
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
const scanIndexDir = body.indexDir ?? deps2.indexDir;
|
|
6740
|
+
if (scanIndexDir) {
|
|
6741
|
+
const resolvedRoot = path10.resolve(deps2.projectRoot);
|
|
6742
|
+
const resolvedIndex = path10.resolve(deps2.projectRoot, scanIndexDir);
|
|
6743
|
+
if (resolvedIndex !== resolvedRoot && !resolvedIndex.startsWith(resolvedRoot + path10.sep)) {
|
|
6744
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6745
|
+
res.end(JSON.stringify({ error: "Invalid indexDir: must be within project root" }));
|
|
6746
|
+
return;
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
const result = runDeadCodeScan(deps2.projectRoot, {
|
|
6750
|
+
indexDir: scanIndexDir,
|
|
6751
|
+
userEntryPoints: body.entryPoints
|
|
6752
|
+
});
|
|
6753
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6754
|
+
res.end(JSON.stringify(result));
|
|
6755
|
+
} catch (err) {
|
|
6756
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6757
|
+
res.end(
|
|
6758
|
+
JSON.stringify({
|
|
6759
|
+
error: "Dead-code scan failed",
|
|
6760
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
6761
|
+
})
|
|
6762
|
+
);
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
function handleDeadCodeActionPlan(res, _deps, req) {
|
|
6766
|
+
return readJsonBody(req).then((raw) => {
|
|
6767
|
+
let parsed;
|
|
6768
|
+
try {
|
|
6769
|
+
parsed = JSON.parse(raw);
|
|
6770
|
+
} catch {
|
|
6771
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6772
|
+
res.end(JSON.stringify({ error: "Invalid scan result JSON" }));
|
|
6773
|
+
return;
|
|
6774
|
+
}
|
|
6775
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.deadPackages) || !Array.isArray(parsed.deadFiles) || !Array.isArray(parsed.deadSymbols)) {
|
|
6776
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6777
|
+
res.end(
|
|
6778
|
+
JSON.stringify({
|
|
6779
|
+
error: "Invalid scan result: missing or malformed required fields (deadPackages, deadFiles, deadSymbols)"
|
|
6780
|
+
})
|
|
6781
|
+
);
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
const result = parsed;
|
|
6785
|
+
const plan = buildActionPlan(result);
|
|
6786
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6787
|
+
res.end(JSON.stringify(plan));
|
|
6788
|
+
}).catch((err) => {
|
|
6789
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6790
|
+
res.end(
|
|
6791
|
+
JSON.stringify({
|
|
6792
|
+
error: "Failed to read request body",
|
|
6793
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
6794
|
+
})
|
|
6795
|
+
);
|
|
6796
|
+
});
|
|
6797
|
+
}
|
|
6798
|
+
function buildActionPlan(result) {
|
|
6799
|
+
const files = /* @__PURE__ */ new Map();
|
|
6800
|
+
for (const dp of result.deadPackages) {
|
|
6801
|
+
const pseudoFile = {
|
|
6802
|
+
file: `${dp.package}/ (package)`,
|
|
6803
|
+
symbolCount: dp.fileCount,
|
|
6804
|
+
symbols: [`remove package ${dp.package} (${dp.fileCount} files, path: ${dp.path})`],
|
|
6805
|
+
priority: 0
|
|
6806
|
+
};
|
|
6807
|
+
files.set(pseudoFile.file, pseudoFile);
|
|
6808
|
+
}
|
|
6809
|
+
for (const df of result.deadFiles) {
|
|
6810
|
+
const existing = files.get(df.file);
|
|
6811
|
+
if (existing) {
|
|
6812
|
+
if (existing.priority > 1) existing.priority = 1;
|
|
6813
|
+
existing.symbolCount += df.symbolCount;
|
|
6814
|
+
continue;
|
|
6815
|
+
}
|
|
6816
|
+
files.set(df.file, {
|
|
6817
|
+
file: df.file,
|
|
6818
|
+
symbolCount: df.symbolCount,
|
|
6819
|
+
symbols: [`entire file (${df.symbolCount} symbols) is dead`],
|
|
6820
|
+
priority: 1
|
|
6821
|
+
});
|
|
6822
|
+
}
|
|
6823
|
+
const deadInAliveFiles = /* @__PURE__ */ new Map();
|
|
6824
|
+
const deadFileSet = new Set(result.deadFiles.map((df) => df.file));
|
|
6825
|
+
for (const ds of result.deadSymbols) {
|
|
6826
|
+
if (deadFileSet.has(ds.file)) continue;
|
|
6827
|
+
const list = deadInAliveFiles.get(ds.file) ?? [];
|
|
6828
|
+
list.push(`${ds.kind} ${ds.name} (line ${ds.line})`);
|
|
6829
|
+
deadInAliveFiles.set(ds.file, list);
|
|
6830
|
+
}
|
|
6831
|
+
for (const [file, symbols] of deadInAliveFiles) {
|
|
6832
|
+
const existing = files.get(file);
|
|
6833
|
+
if (existing) {
|
|
6834
|
+
existing.symbols.push(...symbols);
|
|
6835
|
+
existing.symbolCount += symbols.length;
|
|
6836
|
+
continue;
|
|
6837
|
+
}
|
|
6838
|
+
files.set(file, {
|
|
6839
|
+
file,
|
|
6840
|
+
symbolCount: symbols.length,
|
|
6841
|
+
symbols,
|
|
6842
|
+
priority: 2
|
|
6843
|
+
});
|
|
6844
|
+
}
|
|
6845
|
+
const sorted = [...files.values()].sort((a, b) => a.priority - b.priority || a.file.localeCompare(b.file));
|
|
6846
|
+
return {
|
|
6847
|
+
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.`,
|
|
6848
|
+
files: sorted,
|
|
6849
|
+
totalDeadSymbols: result.stats.dead,
|
|
6850
|
+
totalDeadFiles: result.deadFiles.length,
|
|
6851
|
+
totalDeadPackages: result.deadPackages.length
|
|
6852
|
+
};
|
|
6853
|
+
}
|
|
6854
|
+
|
|
6683
6855
|
// src/server/http-server/api-handlers.ts
|
|
6684
6856
|
async function handleApiSessions(res, globalRoot) {
|
|
6685
6857
|
if (!globalRoot) {
|
|
@@ -6901,8 +7073,8 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
6901
7073
|
res.end(JSON.stringify({ error: String(err) }));
|
|
6902
7074
|
}
|
|
6903
7075
|
}
|
|
6904
|
-
function
|
|
6905
|
-
return new Promise((
|
|
7076
|
+
function readJsonBody2(req) {
|
|
7077
|
+
return new Promise((resolve15, reject) => {
|
|
6906
7078
|
let data = "";
|
|
6907
7079
|
req.on("data", (chunk) => {
|
|
6908
7080
|
data += chunk;
|
|
@@ -6913,7 +7085,7 @@ function readJsonBody(req) {
|
|
|
6913
7085
|
});
|
|
6914
7086
|
req.on("end", () => {
|
|
6915
7087
|
try {
|
|
6916
|
-
|
|
7088
|
+
resolve15(data ? JSON.parse(data) : {});
|
|
6917
7089
|
} catch (err) {
|
|
6918
7090
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
6919
7091
|
}
|
|
@@ -6929,7 +7101,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
6929
7101
|
}
|
|
6930
7102
|
let body;
|
|
6931
7103
|
try {
|
|
6932
|
-
body = await
|
|
7104
|
+
body = await readJsonBody2(req);
|
|
6933
7105
|
} catch {
|
|
6934
7106
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6935
7107
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7031,7 +7203,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
7031
7203
|
}
|
|
7032
7204
|
let body = {};
|
|
7033
7205
|
try {
|
|
7034
|
-
body = await
|
|
7206
|
+
body = await readJsonBody2(req);
|
|
7035
7207
|
} catch {
|
|
7036
7208
|
}
|
|
7037
7209
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
@@ -7072,7 +7244,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7072
7244
|
}
|
|
7073
7245
|
let body;
|
|
7074
7246
|
try {
|
|
7075
|
-
body = await
|
|
7247
|
+
body = await readJsonBody2(req);
|
|
7076
7248
|
} catch {
|
|
7077
7249
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7078
7250
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7136,12 +7308,12 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7136
7308
|
|
|
7137
7309
|
// src/server/projects-manifest.ts
|
|
7138
7310
|
import * as fs8 from "node:fs/promises";
|
|
7139
|
-
import * as
|
|
7311
|
+
import * as path11 from "node:path";
|
|
7140
7312
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7141
7313
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7142
7314
|
function projectsJsonPath(globalConfigPath) {
|
|
7143
|
-
const base =
|
|
7144
|
-
return
|
|
7315
|
+
const base = path11.dirname(globalConfigPath);
|
|
7316
|
+
return path11.join(base, "projects.json");
|
|
7145
7317
|
}
|
|
7146
7318
|
async function loadManifest(globalConfigPath) {
|
|
7147
7319
|
try {
|
|
@@ -7154,37 +7326,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7154
7326
|
}
|
|
7155
7327
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7156
7328
|
const file = projectsJsonPath(globalConfigPath);
|
|
7157
|
-
await fs8.mkdir(
|
|
7329
|
+
await fs8.mkdir(path11.dirname(file), { recursive: true });
|
|
7158
7330
|
await fs8.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7159
7331
|
}
|
|
7160
7332
|
function generateProjectSlug(rootPath) {
|
|
7161
7333
|
return projectSlug(rootPath);
|
|
7162
7334
|
}
|
|
7163
7335
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7164
|
-
const base =
|
|
7165
|
-
const dir =
|
|
7336
|
+
const base = path11.dirname(globalConfigPath);
|
|
7337
|
+
const dir = path11.join(base, "projects", slug);
|
|
7166
7338
|
await fs8.mkdir(dir, { recursive: true });
|
|
7167
7339
|
return dir;
|
|
7168
7340
|
}
|
|
7169
7341
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7170
|
-
const root =
|
|
7342
|
+
const root = path11.resolve(options.projectRoot);
|
|
7171
7343
|
const file = projectsJsonPath(globalConfigPath);
|
|
7172
7344
|
let entry;
|
|
7173
7345
|
await withFileLock(file, async () => {
|
|
7174
7346
|
const manifest = await loadManifest(globalConfigPath);
|
|
7175
7347
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7176
|
-
entry = manifest.projects.find((candidate) =>
|
|
7348
|
+
entry = manifest.projects.find((candidate) => path11.resolve(candidate.root) === root);
|
|
7177
7349
|
if (entry) {
|
|
7178
7350
|
entry.lastSeen = now;
|
|
7179
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7351
|
+
if (options.workingDir) entry.lastWorkingDir = path11.resolve(options.workingDir);
|
|
7180
7352
|
} else {
|
|
7181
7353
|
entry = {
|
|
7182
|
-
name: options.name ??
|
|
7354
|
+
name: options.name ?? path11.basename(root),
|
|
7183
7355
|
root,
|
|
7184
7356
|
slug: generateProjectSlug(root),
|
|
7185
7357
|
createdAt: now,
|
|
7186
7358
|
lastSeen: now,
|
|
7187
|
-
lastWorkingDir: options.workingDir ?
|
|
7359
|
+
lastWorkingDir: options.workingDir ? path11.resolve(options.workingDir) : void 0
|
|
7188
7360
|
};
|
|
7189
7361
|
manifest.projects.push(entry);
|
|
7190
7362
|
}
|
|
@@ -7595,9 +7767,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
7595
7767
|
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
7768
|
}
|
|
7597
7769
|
function isInsideDist(candidate, distDir) {
|
|
7598
|
-
const root =
|
|
7599
|
-
const resolved =
|
|
7600
|
-
return resolved === root || resolved.startsWith(root +
|
|
7770
|
+
const root = path12.resolve(distDir);
|
|
7771
|
+
const resolved = path12.resolve(candidate);
|
|
7772
|
+
return resolved === root || resolved.startsWith(root + path12.sep);
|
|
7601
7773
|
}
|
|
7602
7774
|
function decodeSessionId(segment) {
|
|
7603
7775
|
try {
|
|
@@ -7617,7 +7789,7 @@ function strictDecodeParam(segment, res) {
|
|
|
7617
7789
|
}
|
|
7618
7790
|
function createHttpServer(opts) {
|
|
7619
7791
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
7620
|
-
const distDir =
|
|
7792
|
+
const distDir = path12.resolve(opts.distDir);
|
|
7621
7793
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
7622
7794
|
let techStackRuntime = null;
|
|
7623
7795
|
const getTechStackRuntime = async () => {
|
|
@@ -7836,6 +8008,42 @@ function createHttpServer(opts) {
|
|
|
7836
8008
|
);
|
|
7837
8009
|
return;
|
|
7838
8010
|
}
|
|
8011
|
+
if (url.pathname === "/api/deadcode/scan" && req.method === "POST") {
|
|
8012
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8013
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8014
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
8017
|
+
if (!opts.projectRoot) {
|
|
8018
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8019
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8020
|
+
return;
|
|
8021
|
+
}
|
|
8022
|
+
const deadCodeDeps = {
|
|
8023
|
+
projectRoot: opts.projectRoot,
|
|
8024
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8025
|
+
};
|
|
8026
|
+
await handleDeadCodeScan(res, deadCodeDeps, req);
|
|
8027
|
+
return;
|
|
8028
|
+
}
|
|
8029
|
+
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
8030
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8031
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8032
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8033
|
+
return;
|
|
8034
|
+
}
|
|
8035
|
+
if (!opts.projectRoot) {
|
|
8036
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8037
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8038
|
+
return;
|
|
8039
|
+
}
|
|
8040
|
+
const deadCodeDeps = {
|
|
8041
|
+
projectRoot: opts.projectRoot,
|
|
8042
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8043
|
+
};
|
|
8044
|
+
await handleDeadCodeActionPlan(res, deadCodeDeps, req);
|
|
8045
|
+
return;
|
|
8046
|
+
}
|
|
7839
8047
|
if (url.pathname.startsWith("/api/techstack/")) {
|
|
7840
8048
|
if (requireAccessToken && !accessTokenOk) {
|
|
7841
8049
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -7968,17 +8176,17 @@ function createHttpServer(opts) {
|
|
|
7968
8176
|
}
|
|
7969
8177
|
let filePath;
|
|
7970
8178
|
if (url.pathname === "/" || url.pathname === "") {
|
|
7971
|
-
filePath =
|
|
8179
|
+
filePath = path12.join(distDir, "index.html");
|
|
7972
8180
|
} else {
|
|
7973
|
-
filePath =
|
|
8181
|
+
filePath = path12.join(distDir, url.pathname);
|
|
7974
8182
|
}
|
|
7975
|
-
const resolvedPath =
|
|
8183
|
+
const resolvedPath = path12.resolve(filePath);
|
|
7976
8184
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
7977
8185
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
7978
8186
|
res.end("Forbidden");
|
|
7979
8187
|
return;
|
|
7980
8188
|
}
|
|
7981
|
-
const ext =
|
|
8189
|
+
const ext = path12.extname(resolvedPath);
|
|
7982
8190
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
7983
8191
|
res.setHeader("Content-Type", contentType);
|
|
7984
8192
|
setStaticSecurityHeaders(res);
|
|
@@ -8002,7 +8210,7 @@ function createHttpServer(opts) {
|
|
|
8002
8210
|
} catch (err) {
|
|
8003
8211
|
if (err.code === "ENOENT") {
|
|
8004
8212
|
try {
|
|
8005
|
-
const html = await fs9.readFile(
|
|
8213
|
+
const html = await fs9.readFile(path12.join(distDir, "index.html"), "utf8");
|
|
8006
8214
|
setStaticSecurityHeaders(res);
|
|
8007
8215
|
res.writeHead(200, {
|
|
8008
8216
|
"Content-Type": "text/html",
|
|
@@ -8032,14 +8240,14 @@ function createHttpServer(opts) {
|
|
|
8032
8240
|
|
|
8033
8241
|
// src/server/instance-registry.ts
|
|
8034
8242
|
import * as os from "node:os";
|
|
8035
|
-
import * as
|
|
8243
|
+
import * as path13 from "node:path";
|
|
8036
8244
|
import * as fs10 from "node:fs/promises";
|
|
8037
8245
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8038
8246
|
function defaultBaseDir() {
|
|
8039
|
-
return
|
|
8247
|
+
return path13.join(os.homedir(), ".wrongstack");
|
|
8040
8248
|
}
|
|
8041
8249
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8042
|
-
return
|
|
8250
|
+
return path13.join(baseDir, "webui-instances.json");
|
|
8043
8251
|
}
|
|
8044
8252
|
function isPidAlive(pid) {
|
|
8045
8253
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -11402,16 +11610,16 @@ function createModelOperations(context) {
|
|
|
11402
11610
|
import * as net from "node:net";
|
|
11403
11611
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
11404
11612
|
function isPortFree(host, port) {
|
|
11405
|
-
return new Promise((
|
|
11613
|
+
return new Promise((resolve15) => {
|
|
11406
11614
|
const srv = net.createServer();
|
|
11407
|
-
srv.once("error", () =>
|
|
11615
|
+
srv.once("error", () => resolve15(false));
|
|
11408
11616
|
srv.once("listening", () => {
|
|
11409
|
-
srv.close(() =>
|
|
11617
|
+
srv.close(() => resolve15(true));
|
|
11410
11618
|
});
|
|
11411
11619
|
try {
|
|
11412
11620
|
srv.listen(port, host);
|
|
11413
11621
|
} catch {
|
|
11414
|
-
|
|
11622
|
+
resolve15(false);
|
|
11415
11623
|
}
|
|
11416
11624
|
});
|
|
11417
11625
|
}
|
|
@@ -11703,7 +11911,7 @@ function seedContextMeta(config, context) {
|
|
|
11703
11911
|
meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
|
|
11704
11912
|
meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
|
|
11705
11913
|
meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
|
|
11706
|
-
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] :
|
|
11914
|
+
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 15e3;
|
|
11707
11915
|
meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
|
|
11708
11916
|
meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
|
|
11709
11917
|
const cascade = autoReviewExt?.["cascadeOn"];
|
|
@@ -11723,7 +11931,7 @@ function seedContextMeta(config, context) {
|
|
|
11723
11931
|
|
|
11724
11932
|
// src/server/pref-helpers.ts
|
|
11725
11933
|
import * as fs12 from "node:fs/promises";
|
|
11726
|
-
import * as
|
|
11934
|
+
import * as path14 from "node:path";
|
|
11727
11935
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
11728
11936
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
11729
11937
|
var PREF_KEYS = [
|
|
@@ -11819,7 +12027,7 @@ function prefSnapshot(contextMeta) {
|
|
|
11819
12027
|
return snapshot;
|
|
11820
12028
|
}
|
|
11821
12029
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
11822
|
-
const globalRoot =
|
|
12030
|
+
const globalRoot = path14.dirname(filePath);
|
|
11823
12031
|
await backupConfigFile(filePath, { globalRoot });
|
|
11824
12032
|
let raw;
|
|
11825
12033
|
try {
|
|
@@ -12299,7 +12507,7 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
12299
12507
|
|
|
12300
12508
|
// src/server/project-handlers.ts
|
|
12301
12509
|
import * as fs13 from "node:fs/promises";
|
|
12302
|
-
import * as
|
|
12510
|
+
import * as path15 from "node:path";
|
|
12303
12511
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
12304
12512
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
12305
12513
|
function createProjectHandlers(ctx) {
|
|
@@ -12351,8 +12559,8 @@ function createProjectHandlers(ctx) {
|
|
|
12351
12559
|
});
|
|
12352
12560
|
return;
|
|
12353
12561
|
}
|
|
12354
|
-
const resolved =
|
|
12355
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12562
|
+
const resolved = path15.resolve(parsed.value.root);
|
|
12563
|
+
const name2 = parsed.value.name?.trim() || path15.basename(resolved);
|
|
12356
12564
|
try {
|
|
12357
12565
|
const stat3 = await fs13.stat(resolved).catch(() => null);
|
|
12358
12566
|
if (!stat3?.isDirectory()) {
|
|
@@ -12363,7 +12571,7 @@ function createProjectHandlers(ctx) {
|
|
|
12363
12571
|
return;
|
|
12364
12572
|
}
|
|
12365
12573
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
12366
|
-
const already = before.projects.some((project) =>
|
|
12574
|
+
const already = before.projects.some((project) => path15.resolve(project.root) === resolved);
|
|
12367
12575
|
const entry = await touchProjectInManifest(
|
|
12368
12576
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
12369
12577
|
ctx.globalConfigPath
|
|
@@ -12393,8 +12601,8 @@ function createProjectHandlers(ctx) {
|
|
|
12393
12601
|
});
|
|
12394
12602
|
return;
|
|
12395
12603
|
}
|
|
12396
|
-
const resolved =
|
|
12397
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12604
|
+
const resolved = path15.resolve(parsed.value.root);
|
|
12605
|
+
const name2 = parsed.value.name?.trim() || path15.basename(resolved);
|
|
12398
12606
|
if (!ctx.allowProjectMutations) {
|
|
12399
12607
|
sendTo(ws, {
|
|
12400
12608
|
type: "projects.selected",
|
|
@@ -12429,6 +12637,17 @@ function createProjectHandlers(ctx) {
|
|
|
12429
12637
|
});
|
|
12430
12638
|
const previous = ctx.getSession();
|
|
12431
12639
|
const previousId = previous.id;
|
|
12640
|
+
const previousProjectRoot = ctx.getProjectRoot();
|
|
12641
|
+
const previousPaths = resolveWstackPaths4({
|
|
12642
|
+
projectRoot: previousProjectRoot,
|
|
12643
|
+
globalRoot: ctx.wpaths.globalRoot
|
|
12644
|
+
});
|
|
12645
|
+
const previousIdentityTarget = {
|
|
12646
|
+
projectSlug: previousPaths.projectSlug,
|
|
12647
|
+
projectRoot: previousProjectRoot,
|
|
12648
|
+
projectName: path15.basename(previousProjectRoot),
|
|
12649
|
+
workingDir: ctx.context.workingDir
|
|
12650
|
+
};
|
|
12432
12651
|
const previousUsage = ctx.tokenCounter.total();
|
|
12433
12652
|
const config = ctx.getConfig?.() ?? ctx.config;
|
|
12434
12653
|
const next = await store.create({
|
|
@@ -12454,7 +12673,16 @@ function createProjectHandlers(ctx) {
|
|
|
12454
12673
|
};
|
|
12455
12674
|
try {
|
|
12456
12675
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
12676
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
12457
12677
|
} catch (err) {
|
|
12678
|
+
try {
|
|
12679
|
+
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
12680
|
+
} catch {
|
|
12681
|
+
}
|
|
12682
|
+
try {
|
|
12683
|
+
await ctx.onSessionSwapped?.(previous.id, previousIdentityTarget);
|
|
12684
|
+
} catch {
|
|
12685
|
+
}
|
|
12458
12686
|
await next.close().catch(() => void 0);
|
|
12459
12687
|
await store.delete(next.id).catch(() => void 0);
|
|
12460
12688
|
throw err;
|
|
@@ -13477,6 +13705,7 @@ var CLIENT_WORKSPACE_MESSAGE_TYPES = [
|
|
|
13477
13705
|
var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
|
|
13478
13706
|
"codebase.index.server.shutdown",
|
|
13479
13707
|
"connections.health",
|
|
13708
|
+
"connections.service_action",
|
|
13480
13709
|
"diag.get",
|
|
13481
13710
|
"key.add",
|
|
13482
13711
|
"key.delete",
|
|
@@ -13750,6 +13979,7 @@ var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
|
13750
13979
|
"codebase.index.server.shutdown_result",
|
|
13751
13980
|
"connections.health_error",
|
|
13752
13981
|
"connections.health_result",
|
|
13982
|
+
"connections.service_action_result",
|
|
13753
13983
|
"diag.get",
|
|
13754
13984
|
"key.operation_result",
|
|
13755
13985
|
"model.switch_result",
|
|
@@ -13794,13 +14024,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
13794
14024
|
// src/protocol/decoder.ts
|
|
13795
14025
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
13796
14026
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
13797
|
-
function inspectValue(value,
|
|
14027
|
+
function inspectValue(value, path29, depth) {
|
|
13798
14028
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
13799
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
14029
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path29 };
|
|
13800
14030
|
}
|
|
13801
14031
|
if (value === null || typeof value !== "object") return null;
|
|
13802
14032
|
for (const key of Object.keys(value)) {
|
|
13803
|
-
const childPath = `${
|
|
14033
|
+
const childPath = `${path29}.${key}`;
|
|
13804
14034
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
13805
14035
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
13806
14036
|
}
|
|
@@ -14010,6 +14240,7 @@ function createSessionHandlers(ctx) {
|
|
|
14010
14240
|
ctx.context.session = next;
|
|
14011
14241
|
ctx.context.state.replaceMessages(messages);
|
|
14012
14242
|
await ctx.context.flushConversationJournal?.();
|
|
14243
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, sessionsDirectory());
|
|
14013
14244
|
ctx.context.state.replaceTodos(todos);
|
|
14014
14245
|
resetContextAccounting();
|
|
14015
14246
|
ctx.context.readFiles.clear();
|
|
@@ -14406,7 +14637,10 @@ function createSessionHandlers(ctx) {
|
|
|
14406
14637
|
rollbackClaim = await ctx.claimSession?.(canonicalId);
|
|
14407
14638
|
const resumed = await store.resume(canonicalId);
|
|
14408
14639
|
const restoredTodos = await loadTodosCheckpoint(
|
|
14409
|
-
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json")
|
|
14640
|
+
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json"),
|
|
14641
|
+
ctx.events,
|
|
14642
|
+
ctx.context.traceId,
|
|
14643
|
+
resumed.writer.id
|
|
14410
14644
|
).catch(() => null) ?? [];
|
|
14411
14645
|
activated = true;
|
|
14412
14646
|
await activateSession(
|
|
@@ -14925,7 +15159,7 @@ ${String(p.content ?? "")}`;
|
|
|
14925
15159
|
};
|
|
14926
15160
|
|
|
14927
15161
|
// src/server/codebase-index-server-control.ts
|
|
14928
|
-
import { shutdownCodebaseIndexServer } from "@wrongstack/tools";
|
|
15162
|
+
import { shutdownCodebaseIndexServer as shutdownCodebaseIndexServer2 } from "@wrongstack/tools";
|
|
14929
15163
|
async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
14930
15164
|
if (message.type !== "codebase.index.server.shutdown") return false;
|
|
14931
15165
|
const requestId = message.payload && typeof message.payload === "object" && typeof message.payload.requestId === "string" ? message.payload.requestId : "";
|
|
@@ -14952,7 +15186,7 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
14952
15186
|
});
|
|
14953
15187
|
return true;
|
|
14954
15188
|
}
|
|
14955
|
-
const result = await
|
|
15189
|
+
const result = await shutdownCodebaseIndexServer2(
|
|
14956
15190
|
projectRoot,
|
|
14957
15191
|
deps2.getIndexDir(),
|
|
14958
15192
|
"websocket-request"
|
|
@@ -15493,7 +15727,7 @@ function createRouteFamilyDispatcher(options) {
|
|
|
15493
15727
|
|
|
15494
15728
|
// src/server/shell-open.ts
|
|
15495
15729
|
import * as fs15 from "node:fs/promises";
|
|
15496
|
-
import * as
|
|
15730
|
+
import * as path16 from "node:path";
|
|
15497
15731
|
import { spawn } from "node:child_process";
|
|
15498
15732
|
function normalizeShellOpenTarget(target) {
|
|
15499
15733
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -15504,11 +15738,11 @@ function shellQuote(s) {
|
|
|
15504
15738
|
}
|
|
15505
15739
|
async function handleShellOpen(req, logger, options) {
|
|
15506
15740
|
try {
|
|
15507
|
-
const resolved =
|
|
15741
|
+
const resolved = path16.resolve(req.path);
|
|
15508
15742
|
if (options?.projectRoot) {
|
|
15509
|
-
const root =
|
|
15510
|
-
const relative5 =
|
|
15511
|
-
const escapes = relative5.startsWith("..") ||
|
|
15743
|
+
const root = path16.resolve(options.projectRoot);
|
|
15744
|
+
const relative5 = path16.relative(root, resolved);
|
|
15745
|
+
const escapes = relative5.startsWith("..") || path16.isAbsolute(relative5);
|
|
15512
15746
|
if (escapes) {
|
|
15513
15747
|
return {
|
|
15514
15748
|
success: false,
|
|
@@ -15570,6 +15804,7 @@ async function handleShellOpen(req, logger, options) {
|
|
|
15570
15804
|
import { listBoards as listBoards3 } from "@wrongstack/kanban";
|
|
15571
15805
|
import {
|
|
15572
15806
|
applySddLifecycle,
|
|
15807
|
+
extractVerificationCommand,
|
|
15573
15808
|
SddBoardStore
|
|
15574
15809
|
} from "@wrongstack/sdd";
|
|
15575
15810
|
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -15592,14 +15827,16 @@ var SddBoardWebSocketHandler = class {
|
|
|
15592
15827
|
store;
|
|
15593
15828
|
clients = /* @__PURE__ */ new Set();
|
|
15594
15829
|
lifecycle;
|
|
15830
|
+
security;
|
|
15595
15831
|
diskPollingEnabled;
|
|
15596
15832
|
latest = null;
|
|
15597
15833
|
poll = null;
|
|
15598
15834
|
pollInFlight = false;
|
|
15599
15835
|
unsub = null;
|
|
15600
|
-
constructor(boardsDir, events, lifecycle) {
|
|
15836
|
+
constructor(boardsDir, events, lifecycle, security) {
|
|
15601
15837
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
15602
15838
|
this.lifecycle = lifecycle;
|
|
15839
|
+
this.security = security;
|
|
15603
15840
|
this.diskPollingEnabled = events === void 0;
|
|
15604
15841
|
if (events) {
|
|
15605
15842
|
const handler = (e) => {
|
|
@@ -15645,6 +15882,43 @@ var SddBoardWebSocketHandler = class {
|
|
|
15645
15882
|
return;
|
|
15646
15883
|
}
|
|
15647
15884
|
if (CONTROL_TYPES.has(action)) {
|
|
15885
|
+
const verificationCommands = [];
|
|
15886
|
+
if (action === "set_task_verification") {
|
|
15887
|
+
const command = msg.payload?.verificationCommand;
|
|
15888
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192)) return;
|
|
15889
|
+
if (typeof command === "string" && command.trim()) {
|
|
15890
|
+
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
15891
|
+
}
|
|
15892
|
+
} else if (action === "split_task") {
|
|
15893
|
+
const subtasks = msg.payload?.subtasks;
|
|
15894
|
+
if (Array.isArray(subtasks)) {
|
|
15895
|
+
for (const subtask of subtasks) {
|
|
15896
|
+
if (!subtask || typeof subtask !== "object") continue;
|
|
15897
|
+
const criterion = subtask.successCriterion;
|
|
15898
|
+
if (criterion === void 0) continue;
|
|
15899
|
+
if (typeof criterion !== "string") return;
|
|
15900
|
+
const command = extractVerificationCommand([criterion]);
|
|
15901
|
+
if (!command) continue;
|
|
15902
|
+
if (command.length > 8192) return;
|
|
15903
|
+
verificationCommands.push({ command, operation: "sdd.split_task_verification" });
|
|
15904
|
+
}
|
|
15905
|
+
}
|
|
15906
|
+
}
|
|
15907
|
+
for (const { command, operation } of verificationCommands) {
|
|
15908
|
+
if (!this.security) return;
|
|
15909
|
+
const authorization = await authorizeWebUIAction(
|
|
15910
|
+
this.security.trustBoundary,
|
|
15911
|
+
{
|
|
15912
|
+
capability: "process.spawn",
|
|
15913
|
+
subject: { kind: "command", id: command },
|
|
15914
|
+
risk: "high",
|
|
15915
|
+
cwd: this.lifecycle?.projectRoot,
|
|
15916
|
+
metadata: { operation }
|
|
15917
|
+
},
|
|
15918
|
+
this.security.logger
|
|
15919
|
+
);
|
|
15920
|
+
if (!authorization.allowed) return;
|
|
15921
|
+
}
|
|
15648
15922
|
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
15649
15923
|
if (runId) {
|
|
15650
15924
|
await this.store.appendControl(runId, {
|
|
@@ -15757,7 +16031,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15757
16031
|
};
|
|
15758
16032
|
|
|
15759
16033
|
// src/server/sdd-wizard-wiring.ts
|
|
15760
|
-
import * as
|
|
16034
|
+
import * as path17 from "node:path";
|
|
15761
16035
|
import {
|
|
15762
16036
|
DefaultTaskStore,
|
|
15763
16037
|
TaskTracker
|
|
@@ -15871,7 +16145,7 @@ function buildSddWizardDeps(opts) {
|
|
|
15871
16145
|
}).catch(() => {
|
|
15872
16146
|
projectContext = "";
|
|
15873
16147
|
});
|
|
15874
|
-
const sessionPath = opts.paths.projectSddSession ??
|
|
16148
|
+
const sessionPath = opts.paths.projectSddSession ?? path17.join(opts.paths.projectDir, "sdd-session.json");
|
|
15875
16149
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
15876
16150
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
15877
16151
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -16153,7 +16427,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
16153
16427
|
return;
|
|
16154
16428
|
}
|
|
16155
16429
|
const { runId } = await this.deps.startRun(this.driver, opts);
|
|
16156
|
-
this.driver.setLastRunId(runId);
|
|
16430
|
+
await this.driver.setLastRunId(runId);
|
|
16157
16431
|
if (this.driver.phase() !== "executing" && this.driver.phase() !== "done") {
|
|
16158
16432
|
try {
|
|
16159
16433
|
if (this.driver.phase() === "task_review") await this.driver.approve();
|
|
@@ -16212,7 +16486,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
16212
16486
|
this.lastAgentText = text;
|
|
16213
16487
|
if (this.driver) {
|
|
16214
16488
|
await this.driver.ingestAgentOutput(text);
|
|
16215
|
-
this.driver.setLastAgentText(text);
|
|
16489
|
+
await this.driver.setLastAgentText(text);
|
|
16216
16490
|
}
|
|
16217
16491
|
this.broadcast({ type: "sdd.spec.agent_text", payload: { text } });
|
|
16218
16492
|
} finally {
|
|
@@ -16243,10 +16517,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
16243
16517
|
|
|
16244
16518
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
16245
16519
|
import { watch as fsWatch } from "node:fs";
|
|
16246
|
-
import * as
|
|
16520
|
+
import * as path18 from "node:path";
|
|
16247
16521
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
16248
16522
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
16249
|
-
const globalRoot = globalConfigPath ?
|
|
16523
|
+
const globalRoot = globalConfigPath ? path18.dirname(globalConfigPath) : void 0;
|
|
16250
16524
|
if (!globalRoot) return void 0;
|
|
16251
16525
|
const disposers = [];
|
|
16252
16526
|
const broadcastSessions = async () => {
|
|
@@ -16256,8 +16530,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
16256
16530
|
const sessions = await registry.list();
|
|
16257
16531
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
16258
16532
|
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 :
|
|
16533
|
+
const myRoot = path18.resolve(context.projectRoot);
|
|
16534
|
+
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
16535
|
sessionId: s.sessionId,
|
|
16262
16536
|
projectName: s.projectName,
|
|
16263
16537
|
projectSlug: s.projectSlug,
|
|
@@ -16448,13 +16722,13 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
16448
16722
|
// src/server/setup-events-status-watcher.ts
|
|
16449
16723
|
import { watch as fsWatch2 } from "node:fs";
|
|
16450
16724
|
import * as fs16 from "node:fs/promises";
|
|
16451
|
-
import * as
|
|
16725
|
+
import * as path20 from "node:path";
|
|
16452
16726
|
|
|
16453
16727
|
// src/server/setup-events-watcher.ts
|
|
16454
|
-
import * as
|
|
16728
|
+
import * as path19 from "node:path";
|
|
16455
16729
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
16456
16730
|
const raw = String(filename);
|
|
16457
|
-
const relative5 =
|
|
16731
|
+
const relative5 = path19.isAbsolute(raw) ? path19.relative(projectsDir, raw) : raw;
|
|
16458
16732
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
16459
16733
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
16460
16734
|
return parts.at(-2) ?? null;
|
|
@@ -16489,7 +16763,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
16489
16763
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
16490
16764
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
16491
16765
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
16492
|
-
const projectsDir =
|
|
16766
|
+
const projectsDir = path20.join(wpaths.globalRoot, "projects");
|
|
16493
16767
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
16494
16768
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
16495
16769
|
const DEBOUNCE_MS = 150;
|
|
@@ -16548,7 +16822,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16548
16822
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
16549
16823
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
16550
16824
|
try {
|
|
16551
|
-
const targetFile =
|
|
16825
|
+
const targetFile = path20.join(projectsDir, projectHash, "status.json");
|
|
16552
16826
|
const content = await fs16.readFile(targetFile, "utf-8");
|
|
16553
16827
|
const statusData = JSON.parse(content);
|
|
16554
16828
|
scheduleBroadcast(projectHash, statusData);
|
|
@@ -16607,7 +16881,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16607
16881
|
|
|
16608
16882
|
// src/server/setup-events-core-watchers.ts
|
|
16609
16883
|
import * as fs17 from "node:fs/promises";
|
|
16610
|
-
import * as
|
|
16884
|
+
import * as path21 from "node:path";
|
|
16611
16885
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
16612
16886
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
16613
16887
|
const disposers = [];
|
|
@@ -16643,7 +16917,7 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
16643
16917
|
if (wpaths?.projectStatus) {
|
|
16644
16918
|
try {
|
|
16645
16919
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
16646
|
-
const dir =
|
|
16920
|
+
const dir = path21.dirname(statusFile);
|
|
16647
16921
|
await fs17.mkdir(dir, { recursive: true });
|
|
16648
16922
|
await fs17.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
16649
16923
|
} catch (err) {
|
|
@@ -16833,6 +17107,9 @@ function setupEvents(deps2) {
|
|
|
16833
17107
|
input: scrub(e.input),
|
|
16834
17108
|
fileTargets: extractCodeMapFileTargets(projectRoot || ".", e.name, e.input),
|
|
16835
17109
|
output: scrub(e.output),
|
|
17110
|
+
// SAGE-injected memory rides beside the tool text so the client renders
|
|
17111
|
+
// it as a memory card. Never folded back into `output`.
|
|
17112
|
+
...e.sage && e.sage.length > 0 ? { sage: e.sage.map((line) => scrub(line)) } : {},
|
|
16836
17113
|
outputBytes: e.outputBytes,
|
|
16837
17114
|
outputTokens: e.outputTokens,
|
|
16838
17115
|
outputLines: e.outputLines,
|
|
@@ -17667,17 +17944,24 @@ var SpecsWebSocketHandler = class {
|
|
|
17667
17944
|
// src/server/start-webui.ts
|
|
17668
17945
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17669
17946
|
import * as http2 from "node:http";
|
|
17670
|
-
import * as
|
|
17947
|
+
import * as path28 from "node:path";
|
|
17671
17948
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
17672
17949
|
import { getSharedProjectMailbox as getSharedProjectMailbox4, resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
|
|
17673
17950
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
17674
17951
|
import {
|
|
17952
|
+
attachTodosCheckpoint,
|
|
17675
17953
|
createSessionEventBridge,
|
|
17676
17954
|
resolveSessionLoggingConfig,
|
|
17677
17955
|
watchProviderConfig
|
|
17678
17956
|
} from "@wrongstack/core/storage";
|
|
17679
17957
|
import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } from "@wrongstack/core/types";
|
|
17680
|
-
import {
|
|
17958
|
+
import {
|
|
17959
|
+
expectDefined as expectDefined3,
|
|
17960
|
+
sessionScopedPath as sessionScopedPath3,
|
|
17961
|
+
startHeapWatchdog,
|
|
17962
|
+
toErrorMessage as toErrorMessage13,
|
|
17963
|
+
wstackGlobalRoot as wstackGlobalRoot2
|
|
17964
|
+
} from "@wrongstack/core/utils";
|
|
17681
17965
|
import { makeProviderFromConfig as makeProviderFromConfig3 } from "@wrongstack/providers";
|
|
17682
17966
|
import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
17683
17967
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
@@ -17864,7 +18148,7 @@ function findWorkspaceCliEntry(projectRoot) {
|
|
|
17864
18148
|
return null;
|
|
17865
18149
|
}
|
|
17866
18150
|
function sleep(ms) {
|
|
17867
|
-
return new Promise((
|
|
18151
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
17868
18152
|
}
|
|
17869
18153
|
|
|
17870
18154
|
// src/server/terminal-ws-handler.ts
|
|
@@ -18100,7 +18384,7 @@ function clampDim(value, fallback) {
|
|
|
18100
18384
|
}
|
|
18101
18385
|
|
|
18102
18386
|
// src/server/worktree-ws-handler.ts
|
|
18103
|
-
import { join as join11, resolve as
|
|
18387
|
+
import { join as join11, resolve as resolve12, sep as sep5 } from "node:path";
|
|
18104
18388
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
18105
18389
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
18106
18390
|
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
@@ -18161,13 +18445,13 @@ var WorktreeWebSocketHandler = class {
|
|
|
18161
18445
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
18162
18446
|
/** Absolute managed-worktrees root for this project. */
|
|
18163
18447
|
worktreesRoot() {
|
|
18164
|
-
return
|
|
18448
|
+
return resolve12(join11(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
18165
18449
|
}
|
|
18166
18450
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
18167
18451
|
underRoot(dir) {
|
|
18168
|
-
const abs =
|
|
18452
|
+
const abs = resolve12(dir);
|
|
18169
18453
|
const root = this.worktreesRoot();
|
|
18170
|
-
return abs !== root && abs.startsWith(root +
|
|
18454
|
+
return abs !== root && abs.startsWith(root + sep5);
|
|
18171
18455
|
}
|
|
18172
18456
|
/** Branches of worktrees a live in-session run currently owns. */
|
|
18173
18457
|
liveActiveBranches() {
|
|
@@ -18315,7 +18599,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
18315
18599
|
}
|
|
18316
18600
|
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
18317
18601
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
18318
|
-
const summary = await wt.diffSummary(
|
|
18602
|
+
const summary = await wt.diffSummary(resolve12(dir), base);
|
|
18319
18603
|
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
18320
18604
|
}
|
|
18321
18605
|
// ── internals ───────────────────────────────────────────────────────────
|
|
@@ -18464,7 +18748,9 @@ async function createAgentServices(input) {
|
|
|
18464
18748
|
memory: memoryRetrieval,
|
|
18465
18749
|
maxHintsPerTool: config.Sage?.inject?.maxHintsPerTool,
|
|
18466
18750
|
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
18751
|
+
taskAware: config.Sage?.inject?.taskAware,
|
|
18467
18752
|
minScore: config.Sage?.inject?.minScore,
|
|
18753
|
+
minImportance: config.Sage?.inject?.minImportance,
|
|
18468
18754
|
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
18469
18755
|
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
18470
18756
|
triggers: config.Sage?.inject?.triggers
|
|
@@ -18767,15 +19053,20 @@ async function createAgentServices(input) {
|
|
|
18767
19053
|
projectRoot
|
|
18768
19054
|
);
|
|
18769
19055
|
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
18770
|
-
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
19056
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
19057
|
+
wpaths.projectSddBoards,
|
|
19058
|
+
void 0,
|
|
19059
|
+
{
|
|
19060
|
+
projectRoot,
|
|
19061
|
+
paths: {
|
|
19062
|
+
projectSpecs: wpaths.projectSpecs,
|
|
19063
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
19064
|
+
projectSddSession: wpaths.projectSddSession,
|
|
19065
|
+
projectSddBoards: wpaths.projectSddBoards
|
|
19066
|
+
}
|
|
19067
|
+
},
|
|
19068
|
+
{ trustBoundary: input.trustBoundary, logger }
|
|
19069
|
+
);
|
|
18779
19070
|
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
18780
19071
|
buildSddWizardDeps({
|
|
18781
19072
|
agent,
|
|
@@ -18787,7 +19078,16 @@ async function createAgentServices(input) {
|
|
|
18787
19078
|
providerRegistry,
|
|
18788
19079
|
toolRegistry,
|
|
18789
19080
|
session: input.sessionGetter(),
|
|
18790
|
-
projectRoot
|
|
19081
|
+
projectRoot,
|
|
19082
|
+
// Thread the container-provided ProviderModelStatusTracker so a 429
|
|
19083
|
+
// from this subagent's first call transitions the (provider, model)
|
|
19084
|
+
// pair to `state: 'blocked'` instead of silently no-op'ing. The
|
|
19085
|
+
// runtime container binds a default `ProviderModelStatusTracker`
|
|
19086
|
+
// (see packages/runtime/src/container.ts); without this dep, the
|
|
19087
|
+
// subagent's fallback extension's tracker hooks are undefined and
|
|
19088
|
+
// round-robin keeps reassigning the doomed model. Mirrors the CLI
|
|
19089
|
+
// factory wiring at host-subagent-factory.ts:337.
|
|
19090
|
+
statusTracker: container.safeResolve(TOKENS.ProviderModelStatusTracker)
|
|
18791
19091
|
}),
|
|
18792
19092
|
paths: {
|
|
18793
19093
|
projectSpecs: wpaths.projectSpecs,
|
|
@@ -18930,7 +19230,7 @@ function createConnectionHandler(options) {
|
|
|
18930
19230
|
}
|
|
18931
19231
|
|
|
18932
19232
|
// src/server/message-dispatcher.ts
|
|
18933
|
-
import
|
|
19233
|
+
import path22 from "node:path";
|
|
18934
19234
|
function createMessageDispatcher(opts) {
|
|
18935
19235
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
18936
19236
|
function makeWorklistContext() {
|
|
@@ -18951,7 +19251,7 @@ function createMessageDispatcher(opts) {
|
|
|
18951
19251
|
skillLoader: deps2.skillLoader,
|
|
18952
19252
|
skillInstaller: deps2.skillInstaller,
|
|
18953
19253
|
projectRoot,
|
|
18954
|
-
projectSkillsDir:
|
|
19254
|
+
projectSkillsDir: path22.join(projectRoot, ".wrongstack", "skills"),
|
|
18955
19255
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
18956
19256
|
};
|
|
18957
19257
|
}
|
|
@@ -19203,7 +19503,7 @@ function createMessageDispatcher(opts) {
|
|
|
19203
19503
|
|
|
19204
19504
|
// src/server/pre-context-services.ts
|
|
19205
19505
|
import { createRequire as createRequire3 } from "node:module";
|
|
19206
|
-
import * as
|
|
19506
|
+
import * as path25 from "node:path";
|
|
19207
19507
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
19208
19508
|
import {
|
|
19209
19509
|
getSharedProjectMailbox as getSharedProjectMailbox3,
|
|
@@ -19258,7 +19558,7 @@ import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/too
|
|
|
19258
19558
|
|
|
19259
19559
|
// src/server/model-auto-discovery.ts
|
|
19260
19560
|
import * as fs18 from "node:fs/promises";
|
|
19261
|
-
import * as
|
|
19561
|
+
import * as path23 from "node:path";
|
|
19262
19562
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
19263
19563
|
function isOverlayRegistry(value) {
|
|
19264
19564
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -19294,7 +19594,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19294
19594
|
if (!isOverlayRegistry(registry)) return;
|
|
19295
19595
|
const targets = eligibleProviders(opts.config);
|
|
19296
19596
|
if (targets.length === 0) return;
|
|
19297
|
-
const cacheFile =
|
|
19597
|
+
const cacheFile = path23.join(opts.cacheDir, "discovered-models-cache.json");
|
|
19298
19598
|
const cache2 = await readCache(cacheFile);
|
|
19299
19599
|
let cacheDirty = false;
|
|
19300
19600
|
await Promise.all(
|
|
@@ -19331,7 +19631,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19331
19631
|
);
|
|
19332
19632
|
if (cacheDirty) {
|
|
19333
19633
|
try {
|
|
19334
|
-
await fs18.mkdir(
|
|
19634
|
+
await fs18.mkdir(path23.dirname(cacheFile), { recursive: true });
|
|
19335
19635
|
await fs18.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
19336
19636
|
} catch {
|
|
19337
19637
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
@@ -19428,7 +19728,7 @@ function resolveSetupProvider(opts) {
|
|
|
19428
19728
|
}
|
|
19429
19729
|
|
|
19430
19730
|
// src/server/standalone-session-identity.ts
|
|
19431
|
-
import * as
|
|
19731
|
+
import * as path24 from "node:path";
|
|
19432
19732
|
import {
|
|
19433
19733
|
AgentStatusTracker,
|
|
19434
19734
|
FleetNotifier,
|
|
@@ -19447,7 +19747,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
19447
19747
|
let activeTarget = {
|
|
19448
19748
|
projectSlug: paths.projectSlug,
|
|
19449
19749
|
projectRoot: paths.projectRoot,
|
|
19450
|
-
projectName:
|
|
19750
|
+
projectName: path24.basename(paths.projectRoot),
|
|
19451
19751
|
workingDir: opts.workingDir
|
|
19452
19752
|
};
|
|
19453
19753
|
let pendingClaim;
|
|
@@ -19678,7 +19978,7 @@ async function createPreContextServices(input) {
|
|
|
19678
19978
|
await discoverAndMergeWebuiProviders({
|
|
19679
19979
|
config,
|
|
19680
19980
|
registry: modelsRegistry,
|
|
19681
|
-
cacheDir:
|
|
19981
|
+
cacheDir: path25.dirname(wpaths.modelsCache),
|
|
19682
19982
|
logger
|
|
19683
19983
|
});
|
|
19684
19984
|
} catch (err) {
|
|
@@ -19730,7 +20030,7 @@ async function createPreContextServices(input) {
|
|
|
19730
20030
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
19731
20031
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
19732
20032
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
19733
|
-
|
|
20033
|
+
path25.join(wpaths.projectDir, "mcp-auth.json"),
|
|
19734
20034
|
vault
|
|
19735
20035
|
);
|
|
19736
20036
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -19836,7 +20136,7 @@ async function createPreContextServices(input) {
|
|
|
19836
20136
|
};
|
|
19837
20137
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
19838
20138
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
19839
|
-
manifestPath:
|
|
20139
|
+
manifestPath: path25.join(wpaths.configDir, "installed-skills.json"),
|
|
19840
20140
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
19841
20141
|
globalSkillsDir: wpaths.globalSkills,
|
|
19842
20142
|
projectHash: wpaths.projectHash,
|
|
@@ -19846,8 +20146,8 @@ async function createPreContextServices(input) {
|
|
|
19846
20146
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
19847
20147
|
try {
|
|
19848
20148
|
const req = createRequire3(import.meta.url);
|
|
19849
|
-
return
|
|
19850
|
-
|
|
20149
|
+
return path25.join(
|
|
20150
|
+
path25.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
19851
20151
|
"data",
|
|
19852
20152
|
"prompts"
|
|
19853
20153
|
);
|
|
@@ -19951,7 +20251,7 @@ async function createPreContextServices(input) {
|
|
|
19951
20251
|
}
|
|
19952
20252
|
|
|
19953
20253
|
// src/server/routes.ts
|
|
19954
|
-
import
|
|
20254
|
+
import path26 from "node:path";
|
|
19955
20255
|
import { makeProviderFromConfig as makeProviderFromConfig2, withCatalogCapabilities } from "@wrongstack/providers";
|
|
19956
20256
|
|
|
19957
20257
|
// src/server/mode-handlers.ts
|
|
@@ -20076,6 +20376,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20076
20376
|
config: state.getConfig(),
|
|
20077
20377
|
clients: state.getClients(),
|
|
20078
20378
|
context: deps2.context,
|
|
20379
|
+
events: deps2.events,
|
|
20079
20380
|
toolRegistry: deps2.toolRegistry,
|
|
20080
20381
|
compactor: deps2.compactor,
|
|
20081
20382
|
customModeStore: deps2.customModeStore,
|
|
@@ -20087,6 +20388,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20087
20388
|
setSession: state.setSession,
|
|
20088
20389
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
20089
20390
|
claimSession: cb.claimSession,
|
|
20391
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
20090
20392
|
onSessionSwapped: cb.onSessionSwapped,
|
|
20091
20393
|
abortActiveRun: state.abortRunLock,
|
|
20092
20394
|
isRunActive: state.isRunActive,
|
|
@@ -20108,6 +20410,8 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20108
20410
|
setSessionStore: state.setSessionStore,
|
|
20109
20411
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
20110
20412
|
abortRunLock: state.abortRunLock,
|
|
20413
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
20414
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
20111
20415
|
sessionStartPayload: cb.sessionStartPayload
|
|
20112
20416
|
});
|
|
20113
20417
|
const modeRoutes = createModeHandlers({
|
|
@@ -20239,7 +20543,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20239
20543
|
};
|
|
20240
20544
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
20241
20545
|
getProjectRoot: state.getProjectRoot,
|
|
20242
|
-
getGlobalRoot: () =>
|
|
20546
|
+
getGlobalRoot: () => path26.dirname(deps2.globalConfigPath),
|
|
20243
20547
|
events: deps2.events
|
|
20244
20548
|
});
|
|
20245
20549
|
const mcpRoutes = {
|
|
@@ -20312,7 +20616,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20312
20616
|
}
|
|
20313
20617
|
|
|
20314
20618
|
// src/server/server-runtime.ts
|
|
20315
|
-
import * as
|
|
20619
|
+
import * as path27 from "node:path";
|
|
20316
20620
|
import { createRequire as createRequire4 } from "node:module";
|
|
20317
20621
|
import { fileURLToPath } from "node:url";
|
|
20318
20622
|
import { WebSocketServer } from "ws";
|
|
@@ -20373,7 +20677,7 @@ function createSessionStartPayload(g) {
|
|
|
20373
20677
|
inputCost,
|
|
20374
20678
|
outputCost,
|
|
20375
20679
|
cacheReadCost,
|
|
20376
|
-
projectName:
|
|
20680
|
+
projectName: path27.basename(projectRoot) || projectRoot,
|
|
20377
20681
|
projectRoot,
|
|
20378
20682
|
cwd: g.getWorkingDir(),
|
|
20379
20683
|
mode: g.getModeId(),
|
|
@@ -20461,13 +20765,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
20461
20765
|
};
|
|
20462
20766
|
}
|
|
20463
20767
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
20464
|
-
if (explicitDistDir) return
|
|
20768
|
+
if (explicitDistDir) return path27.resolve(explicitDistDir);
|
|
20465
20769
|
try {
|
|
20466
20770
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
20467
20771
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
20468
|
-
return
|
|
20772
|
+
return path27.dirname(serverEntry);
|
|
20469
20773
|
} catch {
|
|
20470
|
-
return
|
|
20774
|
+
return path27.resolve(path27.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
20471
20775
|
}
|
|
20472
20776
|
}
|
|
20473
20777
|
function startHttpServer(opts) {
|
|
@@ -20498,6 +20802,56 @@ function registerShutdown(deps2) {
|
|
|
20498
20802
|
}
|
|
20499
20803
|
|
|
20500
20804
|
// src/server/start-webui.ts
|
|
20805
|
+
function createStandaloneTodosCheckpointLifecycle(input) {
|
|
20806
|
+
let checkpointSessionId = input.sessionId;
|
|
20807
|
+
let checkpointSessionsDir = input.sessionsDir;
|
|
20808
|
+
const attachCheckpoint = (sessionId, sessionsDir) => attachTodosCheckpoint(
|
|
20809
|
+
input.state,
|
|
20810
|
+
sessionScopedPath3(sessionsDir, sessionId, ".todos.json"),
|
|
20811
|
+
sessionId,
|
|
20812
|
+
input.events,
|
|
20813
|
+
input.traceId,
|
|
20814
|
+
input.warn
|
|
20815
|
+
);
|
|
20816
|
+
let detachCurrent = attachCheckpoint(input.sessionId, input.sessionsDir);
|
|
20817
|
+
let checkpointAttached = true;
|
|
20818
|
+
const detachCurrentCheckpoint = async () => {
|
|
20819
|
+
if (!checkpointAttached) return;
|
|
20820
|
+
checkpointAttached = false;
|
|
20821
|
+
await detachCurrent();
|
|
20822
|
+
};
|
|
20823
|
+
let transitionTail = Promise.resolve();
|
|
20824
|
+
const rebind = (nextSessionId, sessionsDir) => {
|
|
20825
|
+
const transition = transitionTail.then(async () => {
|
|
20826
|
+
if (checkpointAttached && nextSessionId === checkpointSessionId && sessionsDir === checkpointSessionsDir) {
|
|
20827
|
+
return;
|
|
20828
|
+
}
|
|
20829
|
+
let detachFailed = false;
|
|
20830
|
+
let detachError;
|
|
20831
|
+
try {
|
|
20832
|
+
await detachCurrentCheckpoint();
|
|
20833
|
+
} catch (error2) {
|
|
20834
|
+
detachFailed = true;
|
|
20835
|
+
detachError = error2;
|
|
20836
|
+
}
|
|
20837
|
+
const nextDetach = attachCheckpoint(nextSessionId, sessionsDir);
|
|
20838
|
+
checkpointSessionId = nextSessionId;
|
|
20839
|
+
checkpointSessionsDir = sessionsDir;
|
|
20840
|
+
detachCurrent = nextDetach;
|
|
20841
|
+
checkpointAttached = true;
|
|
20842
|
+
if (detachFailed) throw detachError;
|
|
20843
|
+
});
|
|
20844
|
+
transitionTail = transition.catch(() => void 0);
|
|
20845
|
+
return transition;
|
|
20846
|
+
};
|
|
20847
|
+
return {
|
|
20848
|
+
rebind,
|
|
20849
|
+
detach: async () => {
|
|
20850
|
+
await transitionTail;
|
|
20851
|
+
await detachCurrentCheckpoint();
|
|
20852
|
+
}
|
|
20853
|
+
};
|
|
20854
|
+
}
|
|
20501
20855
|
async function startWebUI(opts = {}) {
|
|
20502
20856
|
ensureSessionShell();
|
|
20503
20857
|
const ports = await resolvePorts(opts);
|
|
@@ -20565,6 +20919,14 @@ async function startWebUI(opts = {}) {
|
|
|
20565
20919
|
} = preContext;
|
|
20566
20920
|
let sessionStore = preContext.sessionStore;
|
|
20567
20921
|
let session = preContext.session;
|
|
20922
|
+
const todosCheckpoint = createStandaloneTodosCheckpointLifecycle({
|
|
20923
|
+
state: context.state,
|
|
20924
|
+
sessionsDir: wpaths.projectSessions,
|
|
20925
|
+
sessionId: session.id,
|
|
20926
|
+
events,
|
|
20927
|
+
traceId: context.traceId,
|
|
20928
|
+
warn: (message) => logger.warn(message)
|
|
20929
|
+
});
|
|
20568
20930
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
20569
20931
|
let modeId = preContext.modeId;
|
|
20570
20932
|
const needsSetup = preContext.needsSetup;
|
|
@@ -20691,7 +21053,7 @@ async function startWebUI(opts = {}) {
|
|
|
20691
21053
|
if (events.listenerCount("tool.confirm_needed") === 0) {
|
|
20692
21054
|
throw new Error("No permission confirmation surface is connected");
|
|
20693
21055
|
}
|
|
20694
|
-
const decision = await new Promise((
|
|
21056
|
+
const decision = await new Promise((resolve15) => {
|
|
20695
21057
|
events.emit("tool.confirm_needed", {
|
|
20696
21058
|
sessionId: context.session.id,
|
|
20697
21059
|
tool: confirmTool,
|
|
@@ -20701,7 +21063,7 @@ async function startWebUI(opts = {}) {
|
|
|
20701
21063
|
decisionSource: pending.decisionSource,
|
|
20702
21064
|
riskTier: pending.riskTier,
|
|
20703
21065
|
boundaryReason: pending.boundaryReason,
|
|
20704
|
-
resolve:
|
|
21066
|
+
resolve: resolve15
|
|
20705
21067
|
});
|
|
20706
21068
|
});
|
|
20707
21069
|
const rule = { tool: "language_package", pattern: pending.suggestedPattern };
|
|
@@ -20801,21 +21163,21 @@ async function startWebUI(opts = {}) {
|
|
|
20801
21163
|
});
|
|
20802
21164
|
}
|
|
20803
21165
|
async function touchProjectEntry(root, workDir) {
|
|
20804
|
-
const resolved =
|
|
21166
|
+
const resolved = path28.resolve(root);
|
|
20805
21167
|
const manifest = await loadManifest(globalConfigPath);
|
|
20806
21168
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20807
|
-
const existing = manifest.projects.find((p) =>
|
|
21169
|
+
const existing = manifest.projects.find((p) => path28.resolve(p.root) === resolved);
|
|
20808
21170
|
if (existing) {
|
|
20809
21171
|
existing.lastSeen = now;
|
|
20810
|
-
if (workDir) existing.lastWorkingDir =
|
|
21172
|
+
if (workDir) existing.lastWorkingDir = path28.resolve(workDir);
|
|
20811
21173
|
} else {
|
|
20812
21174
|
manifest.projects.push({
|
|
20813
|
-
name:
|
|
21175
|
+
name: path28.basename(resolved),
|
|
20814
21176
|
root: resolved,
|
|
20815
21177
|
slug: generateProjectSlug(resolved),
|
|
20816
21178
|
createdAt: now,
|
|
20817
21179
|
lastSeen: now,
|
|
20818
|
-
lastWorkingDir: workDir ?
|
|
21180
|
+
lastWorkingDir: workDir ? path28.resolve(workDir) : void 0
|
|
20819
21181
|
});
|
|
20820
21182
|
}
|
|
20821
21183
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -20916,6 +21278,7 @@ async function startWebUI(opts = {}) {
|
|
|
20916
21278
|
const cb = {
|
|
20917
21279
|
sessionStartPayload,
|
|
20918
21280
|
claimSession: (sessionId, target) => sessionIdentity.claim(sessionId, target),
|
|
21281
|
+
onBeforeSessionTodosReplaced: todosCheckpoint.rebind,
|
|
20919
21282
|
onSessionSwapped: async (sessionId, target) => {
|
|
20920
21283
|
await sessionIdentity.activate(sessionId, target);
|
|
20921
21284
|
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
@@ -21066,6 +21429,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21066
21429
|
...wssSecondary ? [wssSecondary] : []
|
|
21067
21430
|
],
|
|
21068
21431
|
onShutdown: async () => {
|
|
21432
|
+
await todosCheckpoint.detach();
|
|
21069
21433
|
await stopHeapWatchdog();
|
|
21070
21434
|
credentialWatcherClose?.();
|
|
21071
21435
|
brainMonitor.stop();
|
|
@@ -21091,7 +21455,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21091
21455
|
await memoryStore.dispose().catch(
|
|
21092
21456
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage13(err)}`)
|
|
21093
21457
|
);
|
|
21094
|
-
await unregisterInstance(process.pid,
|
|
21458
|
+
await unregisterInstance(process.pid, path28.dirname(globalConfigPath));
|
|
21095
21459
|
}
|
|
21096
21460
|
});
|
|
21097
21461
|
}
|