@wrongstack/webui-server 0.296.2 → 0.296.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +837 -238
- package/dist/index.js.map +4 -4
- package/dist/protocol/client-workspace.d.ts +1 -1
- package/dist/protocol/client-workspace.d.ts.map +1 -1
- package/dist/protocol/index.js +4 -1
- package/dist/protocol/index.js.map +2 -2
- package/dist/protocol/projections.d.ts +6 -0
- package/dist/protocol/projections.d.ts.map +1 -1
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/registry.d.ts.map +1 -1
- package/dist/protocol/server-workspace.d.ts +1 -1
- package/dist/protocol/server-workspace.d.ts.map +1 -1
- package/dist/server/backend-services.d.ts.map +1 -1
- package/dist/server/connections-health-route.d.ts +8 -0
- package/dist/server/connections-health-route.d.ts.map +1 -1
- package/dist/server/embedded-host-adapters.d.ts +3 -0
- package/dist/server/embedded-host-adapters.d.ts.map +1 -1
- package/dist/server/embedded-message-router.d.ts.map +1 -1
- package/dist/server/entry.js +559 -209
- package/dist/server/entry.js.map +4 -4
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/project-handlers.d.ts +1 -0
- package/dist/server/project-handlers.d.ts.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/sdd-board-ws-handler.d.ts +8 -1
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -1
- package/dist/server/session-handlers.d.ts +3 -0
- package/dist/server/session-handlers.d.ts.map +1 -1
- package/dist/server/setup-events.d.ts.map +1 -1
- package/dist/server/start-webui.d.ts +12 -0
- package/dist/server/start-webui.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -134,85 +134,85 @@ var ENUM_PREF_KEYS = {
|
|
|
134
134
|
autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
|
|
135
135
|
fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
|
|
136
136
|
};
|
|
137
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
137
|
+
function validateModelRuntimeValue(modelRuntime, path34) {
|
|
138
138
|
const reasoning = modelRuntime["reasoning"];
|
|
139
139
|
if (reasoning !== void 0) {
|
|
140
|
-
if (!isRecord(reasoning)) return `${
|
|
140
|
+
if (!isRecord(reasoning)) return `${path34}.reasoning must be an object when provided`;
|
|
141
141
|
const mode = reasoning["mode"];
|
|
142
142
|
const effort = reasoning["effort"];
|
|
143
143
|
const preserve = reasoning["preserve"];
|
|
144
144
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
145
|
-
return `${
|
|
145
|
+
return `${path34}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
146
146
|
}
|
|
147
147
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
148
|
-
return `${
|
|
148
|
+
return `${path34}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
149
149
|
}
|
|
150
150
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
151
|
-
return `${
|
|
151
|
+
return `${path34}.reasoning.preserve must be a boolean when provided`;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
const cache2 = modelRuntime["cache"];
|
|
155
155
|
if (cache2 !== void 0) {
|
|
156
|
-
if (!isRecord(cache2)) return `${
|
|
156
|
+
if (!isRecord(cache2)) return `${path34}.cache must be an object when provided`;
|
|
157
157
|
const ttl = cache2["ttl"];
|
|
158
158
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
159
|
-
return `${
|
|
159
|
+
return `${path34}.cache.ttl must be one of: 5m, 1h`;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
const parameters = modelRuntime["parameters"];
|
|
163
163
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
164
|
-
return `${
|
|
164
|
+
return `${path34}.parameters must be an object when provided`;
|
|
165
165
|
}
|
|
166
166
|
return null;
|
|
167
167
|
}
|
|
168
|
-
function validateModelBlackoutRule(rule,
|
|
168
|
+
function validateModelBlackoutRule(rule, path34) {
|
|
169
169
|
const id = rule["id"];
|
|
170
170
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
171
|
-
return `${
|
|
171
|
+
return `${path34}.id must be a non-empty string`;
|
|
172
172
|
}
|
|
173
173
|
const start = rule["start"];
|
|
174
174
|
if (typeof start !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(start)) {
|
|
175
|
-
return `${
|
|
175
|
+
return `${path34}.start must be a string in HH:mm (00:00-23:59) format`;
|
|
176
176
|
}
|
|
177
177
|
const end = rule["end"];
|
|
178
178
|
if (typeof end !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(end)) {
|
|
179
|
-
return `${
|
|
179
|
+
return `${path34}.end must be a string in HH:mm (00:00-23:59) format`;
|
|
180
180
|
}
|
|
181
181
|
if (rule["enabled"] !== void 0 && typeof rule["enabled"] !== "boolean") {
|
|
182
|
-
return `${
|
|
182
|
+
return `${path34}.enabled must be a boolean when provided`;
|
|
183
183
|
}
|
|
184
184
|
if (rule["provider"] !== void 0 && typeof rule["provider"] !== "string") {
|
|
185
|
-
return `${
|
|
185
|
+
return `${path34}.provider must be a string when provided`;
|
|
186
186
|
}
|
|
187
187
|
if (rule["model"] !== void 0 && typeof rule["model"] !== "string") {
|
|
188
|
-
return `${
|
|
188
|
+
return `${path34}.model must be a string when provided`;
|
|
189
189
|
}
|
|
190
190
|
if (rule["days"] !== void 0) {
|
|
191
|
-
if (!Array.isArray(rule["days"])) return `${
|
|
191
|
+
if (!Array.isArray(rule["days"])) return `${path34}.days must be an array when provided`;
|
|
192
192
|
const seen = /* @__PURE__ */ new Set();
|
|
193
193
|
for (const d of rule["days"]) {
|
|
194
194
|
if (typeof d !== "number" || !Number.isInteger(d) || d < 0 || d > 6) {
|
|
195
|
-
return `${
|
|
195
|
+
return `${path34}.days elements must be integers 0-6 when provided`;
|
|
196
196
|
}
|
|
197
|
-
if (seen.has(d)) return `${
|
|
197
|
+
if (seen.has(d)) return `${path34}.days contains duplicate day: ${d}`;
|
|
198
198
|
seen.add(d);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
if (rule["timezone"] !== void 0) {
|
|
202
202
|
if (typeof rule["timezone"] !== "string") {
|
|
203
|
-
return `${
|
|
203
|
+
return `${path34}.timezone must be a string when provided`;
|
|
204
204
|
}
|
|
205
205
|
try {
|
|
206
206
|
Intl.DateTimeFormat(void 0, { timeZone: rule["timezone"] });
|
|
207
207
|
} catch {
|
|
208
|
-
return `${
|
|
208
|
+
return `${path34}.timezone is not a valid IANA timezone (e.g. "America/New_York")`;
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
if (rule["label"] !== void 0 && typeof rule["label"] !== "string") {
|
|
212
|
-
return `${
|
|
212
|
+
return `${path34}.label must be a string when provided`;
|
|
213
213
|
}
|
|
214
214
|
if (rule["mode"] !== void 0 && rule["mode"] !== "blackout" && rule["mode"] !== "allow_only") {
|
|
215
|
-
return `${
|
|
215
|
+
return `${path34}.mode must be 'blackout' or 'allow_only' when provided`;
|
|
216
216
|
}
|
|
217
217
|
return null;
|
|
218
218
|
}
|
|
@@ -760,8 +760,8 @@ function validateShellOpenPayload(payload) {
|
|
|
760
760
|
if (!isRecord2(payload)) {
|
|
761
761
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
762
762
|
}
|
|
763
|
-
const
|
|
764
|
-
if (typeof
|
|
763
|
+
const path34 = payload["path"];
|
|
764
|
+
if (typeof path34 !== "string" || path34.trim().length === 0) {
|
|
765
765
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
766
766
|
}
|
|
767
767
|
const target = payload["target"];
|
|
@@ -774,7 +774,7 @@ function validateShellOpenPayload(payload) {
|
|
|
774
774
|
return {
|
|
775
775
|
ok: true,
|
|
776
776
|
value: {
|
|
777
|
-
path:
|
|
777
|
+
path: path34,
|
|
778
778
|
...target !== void 0 ? { target } : {}
|
|
779
779
|
}
|
|
780
780
|
};
|
|
@@ -783,14 +783,14 @@ function validateGitDiffPayload(payload) {
|
|
|
783
783
|
if (!isRecord2(payload)) {
|
|
784
784
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
785
785
|
}
|
|
786
|
-
const
|
|
787
|
-
if (
|
|
786
|
+
const path34 = payload["path"];
|
|
787
|
+
if (path34 === void 0 || path34 === null) {
|
|
788
788
|
return { ok: true, value: { path: "" } };
|
|
789
789
|
}
|
|
790
|
-
if (typeof
|
|
790
|
+
if (typeof path34 !== "string") {
|
|
791
791
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
792
792
|
}
|
|
793
|
-
return { ok: true, value: { path:
|
|
793
|
+
return { ok: true, value: { path: path34 } };
|
|
794
794
|
}
|
|
795
795
|
function validateProjectsAddPayload(payload) {
|
|
796
796
|
if (!isRecord2(payload)) {
|
|
@@ -4376,8 +4376,8 @@ function jsonByteLength(value) {
|
|
|
4376
4376
|
return MAX_PAYLOAD_BYTES + 1;
|
|
4377
4377
|
}
|
|
4378
4378
|
}
|
|
4379
|
-
function error(errors,
|
|
4380
|
-
errors.push({ path:
|
|
4379
|
+
function error(errors, path34, code, message) {
|
|
4380
|
+
errors.push({ path: path34, code, message });
|
|
4381
4381
|
}
|
|
4382
4382
|
function isMessageRole(value) {
|
|
4383
4383
|
return value === "user" || value === "assistant" || value === "system";
|
|
@@ -4385,25 +4385,25 @@ function isMessageRole(value) {
|
|
|
4385
4385
|
function isPlainJsonObject(value) {
|
|
4386
4386
|
return isRecord3(value);
|
|
4387
4387
|
}
|
|
4388
|
-
function validateCacheControl(value,
|
|
4388
|
+
function validateCacheControl(value, path34, errors) {
|
|
4389
4389
|
if (value === void 0) return void 0;
|
|
4390
4390
|
if (!isRecord3(value) || value["type"] !== "ephemeral") {
|
|
4391
|
-
error(errors,
|
|
4391
|
+
error(errors, path34, "INVALID_CACHE_CONTROL", 'cache_control must be { type: "ephemeral" }.');
|
|
4392
4392
|
return void 0;
|
|
4393
4393
|
}
|
|
4394
4394
|
return { type: "ephemeral" };
|
|
4395
4395
|
}
|
|
4396
|
-
function validateProviderMeta(value,
|
|
4396
|
+
function validateProviderMeta(value, path34, errors) {
|
|
4397
4397
|
if (value === void 0) return void 0;
|
|
4398
4398
|
if (!isPlainJsonObject(value)) {
|
|
4399
|
-
error(errors,
|
|
4399
|
+
error(errors, path34, "INVALID_PROVIDER_META", "providerMeta must be a JSON object.");
|
|
4400
4400
|
return void 0;
|
|
4401
4401
|
}
|
|
4402
4402
|
return value;
|
|
4403
4403
|
}
|
|
4404
|
-
function validateBlock(value,
|
|
4404
|
+
function validateBlock(value, path34, errors) {
|
|
4405
4405
|
if (!isRecord3(value)) {
|
|
4406
|
-
error(errors,
|
|
4406
|
+
error(errors, path34, "INVALID_BLOCK", "Content block must be an object.");
|
|
4407
4407
|
return void 0;
|
|
4408
4408
|
}
|
|
4409
4409
|
const type = value["type"];
|
|
@@ -4411,14 +4411,14 @@ function validateBlock(value, path33, errors) {
|
|
|
4411
4411
|
case "text": {
|
|
4412
4412
|
const text2 = value["text"];
|
|
4413
4413
|
if (typeof text2 !== "string") {
|
|
4414
|
-
error(errors, `${
|
|
4414
|
+
error(errors, `${path34}/text`, "INVALID_TEXT", "Text block text must be a string.");
|
|
4415
4415
|
return void 0;
|
|
4416
4416
|
}
|
|
4417
4417
|
if (text2.length > MAX_STRING_LENGTH) {
|
|
4418
|
-
error(errors, `${
|
|
4418
|
+
error(errors, `${path34}/text`, "TEXT_TOO_LARGE", "Text block is too large.");
|
|
4419
4419
|
return void 0;
|
|
4420
4420
|
}
|
|
4421
|
-
const cacheControl = validateCacheControl(value["cache_control"], `${
|
|
4421
|
+
const cacheControl = validateCacheControl(value["cache_control"], `${path34}/cache_control`, errors);
|
|
4422
4422
|
return cacheControl ? { type: "text", text: text2, cache_control: cacheControl } : { type: "text", text: text2 };
|
|
4423
4423
|
}
|
|
4424
4424
|
case "tool_use": {
|
|
@@ -4426,15 +4426,15 @@ function validateBlock(value, path33, errors) {
|
|
|
4426
4426
|
const name2 = value["name"];
|
|
4427
4427
|
const input = value["input"];
|
|
4428
4428
|
if (typeof id !== "string" || id.length === 0) {
|
|
4429
|
-
error(errors, `${
|
|
4429
|
+
error(errors, `${path34}/id`, "INVALID_TOOL_USE_ID", "tool_use.id must be a non-empty string.");
|
|
4430
4430
|
}
|
|
4431
4431
|
if (typeof name2 !== "string" || name2.length === 0) {
|
|
4432
|
-
error(errors, `${
|
|
4432
|
+
error(errors, `${path34}/name`, "INVALID_TOOL_NAME", "tool_use.name must be a non-empty string.");
|
|
4433
4433
|
}
|
|
4434
4434
|
if (!isPlainJsonObject(input)) {
|
|
4435
|
-
error(errors, `${
|
|
4435
|
+
error(errors, `${path34}/input`, "INVALID_TOOL_INPUT", "tool_use.input must be an object.");
|
|
4436
4436
|
}
|
|
4437
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4437
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path34}/providerMeta`, errors);
|
|
4438
4438
|
if (typeof id !== "string" || id.length === 0 || typeof name2 !== "string" || name2.length === 0 || !isPlainJsonObject(input)) {
|
|
4439
4439
|
return void 0;
|
|
4440
4440
|
}
|
|
@@ -4446,18 +4446,18 @@ function validateBlock(value, path33, errors) {
|
|
|
4446
4446
|
const content = value["content"];
|
|
4447
4447
|
const isError = value["is_error"];
|
|
4448
4448
|
if (typeof toolUseId !== "string" || toolUseId.length === 0) {
|
|
4449
|
-
error(errors, `${
|
|
4449
|
+
error(errors, `${path34}/tool_use_id`, "INVALID_TOOL_RESULT_ID", "tool_result.tool_use_id must be a non-empty string.");
|
|
4450
4450
|
}
|
|
4451
4451
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
4452
|
-
error(errors, `${
|
|
4452
|
+
error(errors, `${path34}/name`, "INVALID_TOOL_RESULT_NAME", "tool_result.name must be a string.");
|
|
4453
4453
|
}
|
|
4454
4454
|
if (typeof content !== "string") {
|
|
4455
|
-
error(errors, `${
|
|
4455
|
+
error(errors, `${path34}/content`, "INVALID_TOOL_RESULT_CONTENT", "tool_result.content must be a string.");
|
|
4456
4456
|
} else if (content.length > MAX_STRING_LENGTH) {
|
|
4457
|
-
error(errors, `${
|
|
4457
|
+
error(errors, `${path34}/content`, "TOOL_RESULT_TOO_LARGE", "tool_result.content is too large.");
|
|
4458
4458
|
}
|
|
4459
4459
|
if (isError !== void 0 && typeof isError !== "boolean") {
|
|
4460
|
-
error(errors, `${
|
|
4460
|
+
error(errors, `${path34}/is_error`, "INVALID_TOOL_RESULT_ERROR", "tool_result.is_error must be boolean.");
|
|
4461
4461
|
}
|
|
4462
4462
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || typeof content !== "string") return void 0;
|
|
4463
4463
|
return {
|
|
@@ -4471,25 +4471,25 @@ function validateBlock(value, path33, errors) {
|
|
|
4471
4471
|
case "image": {
|
|
4472
4472
|
const source = value["source"];
|
|
4473
4473
|
if (!isRecord3(source)) {
|
|
4474
|
-
error(errors, `${
|
|
4474
|
+
error(errors, `${path34}/source`, "INVALID_IMAGE_SOURCE", "image.source must be an object.");
|
|
4475
4475
|
return void 0;
|
|
4476
4476
|
}
|
|
4477
4477
|
const sourceType = source["type"];
|
|
4478
4478
|
if (sourceType !== "base64" && sourceType !== "url") {
|
|
4479
|
-
error(errors, `${
|
|
4479
|
+
error(errors, `${path34}/source/type`, "INVALID_IMAGE_SOURCE_TYPE", "image.source.type must be base64 or url.");
|
|
4480
4480
|
return void 0;
|
|
4481
4481
|
}
|
|
4482
4482
|
const mediaType = source["media_type"];
|
|
4483
4483
|
const data = source["data"];
|
|
4484
4484
|
const url = source["url"];
|
|
4485
4485
|
if (mediaType !== void 0 && typeof mediaType !== "string") {
|
|
4486
|
-
error(errors, `${
|
|
4486
|
+
error(errors, `${path34}/source/media_type`, "INVALID_IMAGE_MEDIA_TYPE", "image.source.media_type must be a string.");
|
|
4487
4487
|
}
|
|
4488
4488
|
if (data !== void 0 && typeof data !== "string") {
|
|
4489
|
-
error(errors, `${
|
|
4489
|
+
error(errors, `${path34}/source/data`, "INVALID_IMAGE_DATA", "image.source.data must be a string.");
|
|
4490
4490
|
}
|
|
4491
4491
|
if (url !== void 0 && typeof url !== "string") {
|
|
4492
|
-
error(errors, `${
|
|
4492
|
+
error(errors, `${path34}/source/url`, "INVALID_IMAGE_URL", "image.source.url must be a string.");
|
|
4493
4493
|
}
|
|
4494
4494
|
return {
|
|
4495
4495
|
type: "image",
|
|
@@ -4505,13 +4505,13 @@ function validateBlock(value, path33, errors) {
|
|
|
4505
4505
|
const thinking = value["thinking"];
|
|
4506
4506
|
const signature = value["signature"];
|
|
4507
4507
|
if (typeof thinking !== "string") {
|
|
4508
|
-
error(errors, `${
|
|
4508
|
+
error(errors, `${path34}/thinking`, "INVALID_THINKING", "thinking.thinking must be a string.");
|
|
4509
4509
|
return void 0;
|
|
4510
4510
|
}
|
|
4511
4511
|
if (signature !== void 0 && typeof signature !== "string") {
|
|
4512
|
-
error(errors, `${
|
|
4512
|
+
error(errors, `${path34}/signature`, "INVALID_THINKING_SIGNATURE", "thinking.signature must be a string.");
|
|
4513
4513
|
}
|
|
4514
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4514
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path34}/providerMeta`, errors);
|
|
4515
4515
|
return {
|
|
4516
4516
|
type: "thinking",
|
|
4517
4517
|
thinking,
|
|
@@ -4520,7 +4520,7 @@ function validateBlock(value, path33, errors) {
|
|
|
4520
4520
|
};
|
|
4521
4521
|
}
|
|
4522
4522
|
default:
|
|
4523
|
-
error(errors, `${
|
|
4523
|
+
error(errors, `${path34}/type`, "UNKNOWN_BLOCK_TYPE", `Unknown content block type: ${String(type)}`);
|
|
4524
4524
|
return void 0;
|
|
4525
4525
|
}
|
|
4526
4526
|
}
|
|
@@ -4538,39 +4538,39 @@ function validateContextEditorMessages(value, currentMessageCount = 0) {
|
|
|
4538
4538
|
error(errors, "/messages", "PAYLOAD_TOO_LARGE", "Context editor payload is too large.");
|
|
4539
4539
|
}
|
|
4540
4540
|
value.forEach((item, index) => {
|
|
4541
|
-
const
|
|
4541
|
+
const path34 = `/messages/${index}`;
|
|
4542
4542
|
if (!isRecord3(item)) {
|
|
4543
|
-
error(errors,
|
|
4543
|
+
error(errors, path34, "INVALID_MESSAGE", "Message must be an object.");
|
|
4544
4544
|
return;
|
|
4545
4545
|
}
|
|
4546
4546
|
const role = item["role"];
|
|
4547
4547
|
if (!isMessageRole(role)) {
|
|
4548
|
-
error(errors, `${
|
|
4548
|
+
error(errors, `${path34}/role`, "INVALID_ROLE", "Message role must be user, assistant, or system.");
|
|
4549
4549
|
return;
|
|
4550
4550
|
}
|
|
4551
4551
|
const rawContent = item["content"];
|
|
4552
4552
|
let content;
|
|
4553
4553
|
if (typeof rawContent === "string") {
|
|
4554
4554
|
if (rawContent.length > MAX_STRING_LENGTH) {
|
|
4555
|
-
error(errors, `${
|
|
4555
|
+
error(errors, `${path34}/content`, "CONTENT_TOO_LARGE", "Message content is too large.");
|
|
4556
4556
|
return;
|
|
4557
4557
|
}
|
|
4558
4558
|
content = rawContent;
|
|
4559
4559
|
} else if (Array.isArray(rawContent)) {
|
|
4560
4560
|
const blocks = [];
|
|
4561
4561
|
rawContent.forEach((block, blockIndex) => {
|
|
4562
|
-
const parsed = validateBlock(block, `${
|
|
4562
|
+
const parsed = validateBlock(block, `${path34}/content/${blockIndex}`, errors);
|
|
4563
4563
|
if (parsed) blocks.push(parsed);
|
|
4564
4564
|
});
|
|
4565
4565
|
content = blocks;
|
|
4566
4566
|
} else {
|
|
4567
|
-
error(errors, `${
|
|
4567
|
+
error(errors, `${path34}/content`, "INVALID_CONTENT", "Message content must be a string or content block array.");
|
|
4568
4568
|
return;
|
|
4569
4569
|
}
|
|
4570
4570
|
const ts = item["ts"];
|
|
4571
4571
|
if (ts !== void 0) {
|
|
4572
4572
|
if (typeof ts !== "string" || Number.isNaN(Date.parse(ts))) {
|
|
4573
|
-
error(errors, `${
|
|
4573
|
+
error(errors, `${path34}/ts`, "INVALID_TIMESTAMP", "Message ts must be an ISO-like timestamp string.");
|
|
4574
4574
|
return;
|
|
4575
4575
|
}
|
|
4576
4576
|
}
|
|
@@ -4944,7 +4944,11 @@ function createConnectionLifecycle(options) {
|
|
|
4944
4944
|
}
|
|
4945
4945
|
|
|
4946
4946
|
// src/server/connections-health-route.ts
|
|
4947
|
-
import {
|
|
4947
|
+
import {
|
|
4948
|
+
ChronicleProjectServerClient,
|
|
4949
|
+
createChronicleProjectAccess as createChronicleProjectAccess2,
|
|
4950
|
+
resolveChronicleProjectServerOptions
|
|
4951
|
+
} from "@wrongstack/core/chronicle";
|
|
4948
4952
|
import {
|
|
4949
4953
|
isMailboxProjectServerAvailable,
|
|
4950
4954
|
MailboxProjectServerConnection
|
|
@@ -4952,7 +4956,11 @@ import {
|
|
|
4952
4956
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
|
|
4953
4957
|
import { getKanbanServerConnection } from "@wrongstack/kanban";
|
|
4954
4958
|
import { isSageProjectServerAvailable, SageProjectServerConnection } from "@wrongstack/sage";
|
|
4955
|
-
import {
|
|
4959
|
+
import {
|
|
4960
|
+
checkCodebaseIndexServerHealth,
|
|
4961
|
+
getIndexState,
|
|
4962
|
+
shutdownCodebaseIndexServer
|
|
4963
|
+
} from "@wrongstack/tools";
|
|
4956
4964
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
4957
4965
|
if (message.type !== "connections.health") return false;
|
|
4958
4966
|
try {
|
|
@@ -5269,6 +5277,244 @@ async function mailboxHealth(projectRoot) {
|
|
|
5269
5277
|
connection.close();
|
|
5270
5278
|
}
|
|
5271
5279
|
}
|
|
5280
|
+
async function handleConnectionsServiceAction(ws, message, context) {
|
|
5281
|
+
if (message.type !== "connections.service_action") return false;
|
|
5282
|
+
const payload = message.payload;
|
|
5283
|
+
const serviceId = payload?.serviceId;
|
|
5284
|
+
const rawAction = payload?.action ?? "shutdown";
|
|
5285
|
+
if (!serviceId) {
|
|
5286
|
+
context.send(ws, {
|
|
5287
|
+
type: "connections.service_action_result",
|
|
5288
|
+
payload: {
|
|
5289
|
+
serviceId: null,
|
|
5290
|
+
action: rawAction,
|
|
5291
|
+
success: false,
|
|
5292
|
+
message: "Missing serviceId in payload"
|
|
5293
|
+
}
|
|
5294
|
+
});
|
|
5295
|
+
return true;
|
|
5296
|
+
}
|
|
5297
|
+
if (rawAction !== "shutdown") {
|
|
5298
|
+
context.send(ws, {
|
|
5299
|
+
type: "connections.service_action_result",
|
|
5300
|
+
payload: {
|
|
5301
|
+
serviceId,
|
|
5302
|
+
action: rawAction,
|
|
5303
|
+
success: false,
|
|
5304
|
+
message: `Unsupported action "${rawAction}" \u2014 only "shutdown" is currently supported`
|
|
5305
|
+
}
|
|
5306
|
+
});
|
|
5307
|
+
return true;
|
|
5308
|
+
}
|
|
5309
|
+
const action = rawAction;
|
|
5310
|
+
if (serviceId === "webui") {
|
|
5311
|
+
context.send(ws, {
|
|
5312
|
+
type: "connections.service_action_result",
|
|
5313
|
+
payload: {
|
|
5314
|
+
serviceId: "webui",
|
|
5315
|
+
action,
|
|
5316
|
+
success: false,
|
|
5317
|
+
message: "Cannot shut down the WebUI transport itself"
|
|
5318
|
+
}
|
|
5319
|
+
});
|
|
5320
|
+
return true;
|
|
5321
|
+
}
|
|
5322
|
+
try {
|
|
5323
|
+
const result = await executeServiceAction(
|
|
5324
|
+
serviceId,
|
|
5325
|
+
action,
|
|
5326
|
+
context.getProjectRoot(),
|
|
5327
|
+
context.getIndexDir()
|
|
5328
|
+
);
|
|
5329
|
+
context.send(ws, {
|
|
5330
|
+
type: "connections.service_action_result",
|
|
5331
|
+
payload: result
|
|
5332
|
+
});
|
|
5333
|
+
} catch (error2) {
|
|
5334
|
+
context.send(ws, {
|
|
5335
|
+
type: "connections.service_action_result",
|
|
5336
|
+
payload: {
|
|
5337
|
+
serviceId,
|
|
5338
|
+
action,
|
|
5339
|
+
success: false,
|
|
5340
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5341
|
+
}
|
|
5342
|
+
});
|
|
5343
|
+
}
|
|
5344
|
+
return true;
|
|
5345
|
+
}
|
|
5346
|
+
async function executeServiceAction(serviceId, action, projectRoot, indexDir) {
|
|
5347
|
+
switch (serviceId) {
|
|
5348
|
+
case "kanban":
|
|
5349
|
+
return killKanbanServer(projectRoot, action);
|
|
5350
|
+
case "sage":
|
|
5351
|
+
return killSageServer(projectRoot, action);
|
|
5352
|
+
case "chronicle":
|
|
5353
|
+
return killChronicleServer(projectRoot, action);
|
|
5354
|
+
case "codebase-index":
|
|
5355
|
+
return killCodebaseIndexServer(projectRoot, indexDir, action);
|
|
5356
|
+
case "mailbox":
|
|
5357
|
+
return killMailboxServer(projectRoot, action);
|
|
5358
|
+
default:
|
|
5359
|
+
return {
|
|
5360
|
+
serviceId,
|
|
5361
|
+
action,
|
|
5362
|
+
success: false,
|
|
5363
|
+
message: `Unknown service: ${serviceId}`
|
|
5364
|
+
};
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
async function killKanbanServer(projectRoot, action) {
|
|
5368
|
+
if (process.env["WRONGSTACK_KANBAN_SERVER"] === "0") {
|
|
5369
|
+
return {
|
|
5370
|
+
serviceId: "kanban",
|
|
5371
|
+
action,
|
|
5372
|
+
success: false,
|
|
5373
|
+
message: "Kanban IPC daemon is disabled via WRONGSTACK_KANBAN_SERVER=0"
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
let connection;
|
|
5377
|
+
try {
|
|
5378
|
+
connection = await getKanbanServerConnection(projectRoot);
|
|
5379
|
+
} catch (error2) {
|
|
5380
|
+
return {
|
|
5381
|
+
serviceId: "kanban",
|
|
5382
|
+
action,
|
|
5383
|
+
success: false,
|
|
5384
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5385
|
+
};
|
|
5386
|
+
}
|
|
5387
|
+
if (!connection) {
|
|
5388
|
+
return {
|
|
5389
|
+
serviceId: "kanban",
|
|
5390
|
+
action,
|
|
5391
|
+
success: false,
|
|
5392
|
+
message: "Kanban IPC daemon is not running"
|
|
5393
|
+
};
|
|
5394
|
+
}
|
|
5395
|
+
try {
|
|
5396
|
+
const result = await connection.request("shutdown", {
|
|
5397
|
+
reason: `WebUI request: ${action}`
|
|
5398
|
+
});
|
|
5399
|
+
return {
|
|
5400
|
+
serviceId: "kanban",
|
|
5401
|
+
action,
|
|
5402
|
+
success: result.stopping,
|
|
5403
|
+
message: result.stopping ? `Kanban IPC daemon ${action} requested` : `Kanban IPC daemon ${action} failed (shutdown not confirmed)`
|
|
5404
|
+
};
|
|
5405
|
+
} catch (error2) {
|
|
5406
|
+
return {
|
|
5407
|
+
serviceId: "kanban",
|
|
5408
|
+
action,
|
|
5409
|
+
success: false,
|
|
5410
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
async function killSageServer(projectRoot, action) {
|
|
5415
|
+
if (!isSageProjectServerAvailable()) {
|
|
5416
|
+
return {
|
|
5417
|
+
serviceId: "sage",
|
|
5418
|
+
action,
|
|
5419
|
+
success: false,
|
|
5420
|
+
message: "SAGE project server is unavailable in this runtime"
|
|
5421
|
+
};
|
|
5422
|
+
}
|
|
5423
|
+
const connection = new SageProjectServerConnection(projectRoot);
|
|
5424
|
+
try {
|
|
5425
|
+
const result = await connection.shutdown(`WebUI request: ${action}`);
|
|
5426
|
+
return {
|
|
5427
|
+
serviceId: "sage",
|
|
5428
|
+
action,
|
|
5429
|
+
success: result.stopped,
|
|
5430
|
+
message: result.stopped ? `SAGE memory server ${action} requested` : `SAGE memory server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5431
|
+
};
|
|
5432
|
+
} catch (error2) {
|
|
5433
|
+
return {
|
|
5434
|
+
serviceId: "sage",
|
|
5435
|
+
action,
|
|
5436
|
+
success: false,
|
|
5437
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5438
|
+
};
|
|
5439
|
+
} finally {
|
|
5440
|
+
connection.close();
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
async function killChronicleServer(projectRoot, action) {
|
|
5444
|
+
const options = resolveChronicleProjectServerOptions({ projectRoot });
|
|
5445
|
+
const client = new ChronicleProjectServerClient(options);
|
|
5446
|
+
try {
|
|
5447
|
+
const result = await client.shutdown(`WebUI request: ${action}`);
|
|
5448
|
+
return {
|
|
5449
|
+
serviceId: "chronicle",
|
|
5450
|
+
action,
|
|
5451
|
+
success: result.stopped,
|
|
5452
|
+
message: result.stopped ? `Chronicle telemetry server ${action} requested` : `Chronicle telemetry server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5453
|
+
};
|
|
5454
|
+
} catch (error2) {
|
|
5455
|
+
return {
|
|
5456
|
+
serviceId: "chronicle",
|
|
5457
|
+
action,
|
|
5458
|
+
success: false,
|
|
5459
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5460
|
+
};
|
|
5461
|
+
} finally {
|
|
5462
|
+
client.close();
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
async function killCodebaseIndexServer(projectRoot, indexDir, action) {
|
|
5466
|
+
try {
|
|
5467
|
+
const result = await shutdownCodebaseIndexServer(
|
|
5468
|
+
projectRoot,
|
|
5469
|
+
indexDir,
|
|
5470
|
+
`websocket-request:${action}`
|
|
5471
|
+
);
|
|
5472
|
+
return {
|
|
5473
|
+
serviceId: "codebase-index",
|
|
5474
|
+
action,
|
|
5475
|
+
success: result.stopped,
|
|
5476
|
+
message: result.stopped ? `Codebase index server ${action} requested` : `Codebase index server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5477
|
+
};
|
|
5478
|
+
} catch (error2) {
|
|
5479
|
+
return {
|
|
5480
|
+
serviceId: "codebase-index",
|
|
5481
|
+
action,
|
|
5482
|
+
success: false,
|
|
5483
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5484
|
+
};
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
async function killMailboxServer(projectRoot, action) {
|
|
5488
|
+
if (!isMailboxProjectServerAvailable()) {
|
|
5489
|
+
return {
|
|
5490
|
+
serviceId: "mailbox",
|
|
5491
|
+
action,
|
|
5492
|
+
success: false,
|
|
5493
|
+
message: "Mailbox project server is unavailable in this runtime"
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
const connection = new MailboxProjectServerConnection(
|
|
5497
|
+
resolveWstackPaths2({ projectRoot }).projectDir
|
|
5498
|
+
);
|
|
5499
|
+
try {
|
|
5500
|
+
const result = await connection.shutdown(`WebUI request: ${action}`);
|
|
5501
|
+
return {
|
|
5502
|
+
serviceId: "mailbox",
|
|
5503
|
+
action,
|
|
5504
|
+
success: result.stopped,
|
|
5505
|
+
message: result.stopped ? `Mailbox IPC server ${action} requested` : `Mailbox IPC server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5506
|
+
};
|
|
5507
|
+
} catch (error2) {
|
|
5508
|
+
return {
|
|
5509
|
+
serviceId: "mailbox",
|
|
5510
|
+
action,
|
|
5511
|
+
success: false,
|
|
5512
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5513
|
+
};
|
|
5514
|
+
} finally {
|
|
5515
|
+
connection.close();
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5272
5518
|
function failureService(id, label, required, mode, error2, latencyMs) {
|
|
5273
5519
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
5274
5520
|
return {
|
|
@@ -5429,9 +5675,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5429
5675
|
const cwd = projectRoot || void 0;
|
|
5430
5676
|
try {
|
|
5431
5677
|
const { execFile: ef } = await import("node:child_process");
|
|
5432
|
-
const git = (args) => new Promise((
|
|
5678
|
+
const git = (args) => new Promise((resolve16) => {
|
|
5433
5679
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
5434
|
-
|
|
5680
|
+
resolve16(err ? "" : stdout.trim());
|
|
5435
5681
|
});
|
|
5436
5682
|
});
|
|
5437
5683
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -5457,12 +5703,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5457
5703
|
function makeGit(cwd) {
|
|
5458
5704
|
return async (args) => {
|
|
5459
5705
|
const { execFile: ef } = await import("node:child_process");
|
|
5460
|
-
return new Promise((
|
|
5706
|
+
return new Promise((resolve16) => {
|
|
5461
5707
|
ef(
|
|
5462
5708
|
"git",
|
|
5463
5709
|
args,
|
|
5464
5710
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
5465
|
-
(err, stdout) =>
|
|
5711
|
+
(err, stdout) => resolve16(err ? "" : stdout)
|
|
5466
5712
|
);
|
|
5467
5713
|
});
|
|
5468
5714
|
};
|
|
@@ -5486,15 +5732,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5486
5732
|
if (!m) continue;
|
|
5487
5733
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5488
5734
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5489
|
-
let
|
|
5490
|
-
if (
|
|
5735
|
+
let path34 = m[3] ?? "";
|
|
5736
|
+
if (path34 === "") {
|
|
5491
5737
|
i += 1;
|
|
5492
|
-
|
|
5738
|
+
path34 = parts[i + 1] ?? parts[i] ?? "";
|
|
5493
5739
|
i += 1;
|
|
5494
5740
|
}
|
|
5495
|
-
if (!
|
|
5496
|
-
const prev = counts.get(
|
|
5497
|
-
counts.set(
|
|
5741
|
+
if (!path34) continue;
|
|
5742
|
+
const prev = counts.get(path34) ?? { added: 0, deleted: 0 };
|
|
5743
|
+
counts.set(path34, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5498
5744
|
}
|
|
5499
5745
|
};
|
|
5500
5746
|
parseNumstat(unstagedNumstat);
|
|
@@ -5506,7 +5752,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5506
5752
|
if (!rec || rec.length < 3) continue;
|
|
5507
5753
|
const x = rec[0] ?? " ";
|
|
5508
5754
|
const y = rec[1] ?? " ";
|
|
5509
|
-
const
|
|
5755
|
+
const path34 = rec.slice(3);
|
|
5510
5756
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5511
5757
|
if (isRename) i += 1;
|
|
5512
5758
|
let status;
|
|
@@ -5518,13 +5764,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5518
5764
|
else if (x === "D" || y === "D") status = "D";
|
|
5519
5765
|
else status = "M";
|
|
5520
5766
|
const staged = x !== " " && x !== "?";
|
|
5521
|
-
let added = counts.get(
|
|
5522
|
-
let deleted = counts.get(
|
|
5767
|
+
let added = counts.get(path34)?.added ?? 0;
|
|
5768
|
+
let deleted = counts.get(path34)?.deleted ?? 0;
|
|
5523
5769
|
if (status === "?") {
|
|
5524
5770
|
added = 0;
|
|
5525
5771
|
deleted = 0;
|
|
5526
5772
|
}
|
|
5527
|
-
files.push({ path:
|
|
5773
|
+
files.push({ path: path34, status, added, deleted, staged });
|
|
5528
5774
|
}
|
|
5529
5775
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5530
5776
|
} catch (err) {
|
|
@@ -5535,10 +5781,10 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5535
5781
|
}
|
|
5536
5782
|
}
|
|
5537
5783
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5538
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5784
|
+
async function handleGitDiff(ws, projectRoot, path34) {
|
|
5539
5785
|
const cwd = projectRoot || void 0;
|
|
5540
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5541
|
-
if (!
|
|
5786
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path34, ...extra } });
|
|
5787
|
+
if (!path34 || path34.includes("\0") || path34.includes("..") || nodePath.isAbsolute(path34)) {
|
|
5542
5788
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5543
5789
|
return;
|
|
5544
5790
|
}
|
|
@@ -5546,10 +5792,10 @@ async function handleGitDiff(ws, projectRoot, path33) {
|
|
|
5546
5792
|
const git = makeGit(cwd);
|
|
5547
5793
|
const { readFile: readFile13 } = await import("node:fs/promises");
|
|
5548
5794
|
const { join: join18 } = await import("node:path");
|
|
5549
|
-
const oldText = await git(["show", `HEAD:${
|
|
5795
|
+
const oldText = await git(["show", `HEAD:${path34}`]);
|
|
5550
5796
|
let newText = "";
|
|
5551
5797
|
try {
|
|
5552
|
-
const abs = cwd ? join18(cwd,
|
|
5798
|
+
const abs = cwd ? join18(cwd, path34) : path34;
|
|
5553
5799
|
const buf = await readFile13(abs);
|
|
5554
5800
|
if (buf.includes(0)) {
|
|
5555
5801
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5629,7 +5875,7 @@ import { execFile } from "node:child_process";
|
|
|
5629
5875
|
var GIT_TIMEOUT_MS = 1e4;
|
|
5630
5876
|
var GIT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
5631
5877
|
function gitStdout(cwd, args) {
|
|
5632
|
-
return new Promise((
|
|
5878
|
+
return new Promise((resolve16) => {
|
|
5633
5879
|
execFile(
|
|
5634
5880
|
"git",
|
|
5635
5881
|
[...args],
|
|
@@ -5640,7 +5886,7 @@ function gitStdout(cwd, args) {
|
|
|
5640
5886
|
timeout: GIT_TIMEOUT_MS,
|
|
5641
5887
|
maxBuffer: GIT_MAX_OUTPUT_BYTES
|
|
5642
5888
|
},
|
|
5643
|
-
(error2, stdout) =>
|
|
5889
|
+
(error2, stdout) => resolve16(error2 ? null : stdout)
|
|
5644
5890
|
);
|
|
5645
5891
|
});
|
|
5646
5892
|
}
|
|
@@ -5906,13 +6152,13 @@ var GoalWebSocketHandler = class {
|
|
|
5906
6152
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
5907
6153
|
try {
|
|
5908
6154
|
const { exec } = await import("node:child_process");
|
|
5909
|
-
const result = await new Promise((
|
|
6155
|
+
const result = await new Promise((resolve16) => {
|
|
5910
6156
|
exec("npx tsc --noEmit", { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
5911
6157
|
if (err && err.code === "ENOENT") {
|
|
5912
|
-
|
|
6158
|
+
resolve16("[verify] tsc not found \u2014 skipping");
|
|
5913
6159
|
return;
|
|
5914
6160
|
}
|
|
5915
|
-
|
|
6161
|
+
resolve16(stdout + stderr);
|
|
5916
6162
|
});
|
|
5917
6163
|
});
|
|
5918
6164
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
@@ -6591,7 +6837,7 @@ function pushEvent(event) {
|
|
|
6591
6837
|
}
|
|
6592
6838
|
}
|
|
6593
6839
|
function parseBody(req) {
|
|
6594
|
-
return new Promise((
|
|
6840
|
+
return new Promise((resolve16, reject) => {
|
|
6595
6841
|
let body = "";
|
|
6596
6842
|
let bodyBytes = 0;
|
|
6597
6843
|
let tooLarge = false;
|
|
@@ -6611,7 +6857,7 @@ function parseBody(req) {
|
|
|
6611
6857
|
return;
|
|
6612
6858
|
}
|
|
6613
6859
|
try {
|
|
6614
|
-
|
|
6860
|
+
resolve16(JSON.parse(body));
|
|
6615
6861
|
} catch {
|
|
6616
6862
|
reject(new Error("Invalid JSON"));
|
|
6617
6863
|
}
|
|
@@ -6696,7 +6942,7 @@ function getAnalyticsBuffer() {
|
|
|
6696
6942
|
// src/server/http-server.ts
|
|
6697
6943
|
import * as fs9 from "node:fs/promises";
|
|
6698
6944
|
import * as http from "node:http";
|
|
6699
|
-
import * as
|
|
6945
|
+
import * as path12 from "node:path";
|
|
6700
6946
|
import * as v8 from "node:v8";
|
|
6701
6947
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6702
6948
|
|
|
@@ -6776,6 +7022,156 @@ async function handleCodemapSymbols(res, deps2, file) {
|
|
|
6776
7022
|
);
|
|
6777
7023
|
}
|
|
6778
7024
|
|
|
7025
|
+
// src/server/deadcode-handlers.ts
|
|
7026
|
+
import * as path10 from "node:path";
|
|
7027
|
+
import { runDeadCodeScan } from "@wrongstack/tools/codebase-index";
|
|
7028
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
7029
|
+
function readJsonBody(req) {
|
|
7030
|
+
return new Promise((resolve16, reject) => {
|
|
7031
|
+
const chunks = [];
|
|
7032
|
+
let total = 0;
|
|
7033
|
+
req.on("data", (chunk) => {
|
|
7034
|
+
total += chunk.length;
|
|
7035
|
+
if (total > MAX_BODY_BYTES) {
|
|
7036
|
+
req.destroy(new Error("Request body too large"));
|
|
7037
|
+
reject(new Error("Request body exceeds 10 MiB limit"));
|
|
7038
|
+
return;
|
|
7039
|
+
}
|
|
7040
|
+
chunks.push(chunk);
|
|
7041
|
+
});
|
|
7042
|
+
req.on("end", () => resolve16(Buffer.concat(chunks).toString("utf8")));
|
|
7043
|
+
req.on("error", (err) => reject(err));
|
|
7044
|
+
});
|
|
7045
|
+
}
|
|
7046
|
+
async function handleDeadCodeScan(res, deps2, req) {
|
|
7047
|
+
try {
|
|
7048
|
+
let body = {};
|
|
7049
|
+
const raw = await readJsonBody(req);
|
|
7050
|
+
if (raw) {
|
|
7051
|
+
try {
|
|
7052
|
+
body = JSON.parse(raw);
|
|
7053
|
+
} catch {
|
|
7054
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7055
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
7056
|
+
return;
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
const scanIndexDir = body.indexDir ?? deps2.indexDir;
|
|
7060
|
+
if (scanIndexDir) {
|
|
7061
|
+
const resolvedRoot = path10.resolve(deps2.projectRoot);
|
|
7062
|
+
const resolvedIndex = path10.resolve(deps2.projectRoot, scanIndexDir);
|
|
7063
|
+
if (resolvedIndex !== resolvedRoot && !resolvedIndex.startsWith(resolvedRoot + path10.sep)) {
|
|
7064
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7065
|
+
res.end(JSON.stringify({ error: "Invalid indexDir: must be within project root" }));
|
|
7066
|
+
return;
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
const result = runDeadCodeScan(deps2.projectRoot, {
|
|
7070
|
+
indexDir: scanIndexDir,
|
|
7071
|
+
userEntryPoints: body.entryPoints
|
|
7072
|
+
});
|
|
7073
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7074
|
+
res.end(JSON.stringify(result));
|
|
7075
|
+
} catch (err) {
|
|
7076
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
7077
|
+
res.end(
|
|
7078
|
+
JSON.stringify({
|
|
7079
|
+
error: "Dead-code scan failed",
|
|
7080
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
7081
|
+
})
|
|
7082
|
+
);
|
|
7083
|
+
}
|
|
7084
|
+
}
|
|
7085
|
+
function handleDeadCodeActionPlan(res, _deps, req) {
|
|
7086
|
+
return readJsonBody(req).then((raw) => {
|
|
7087
|
+
let parsed;
|
|
7088
|
+
try {
|
|
7089
|
+
parsed = JSON.parse(raw);
|
|
7090
|
+
} catch {
|
|
7091
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7092
|
+
res.end(JSON.stringify({ error: "Invalid scan result JSON" }));
|
|
7093
|
+
return;
|
|
7094
|
+
}
|
|
7095
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.deadPackages) || !Array.isArray(parsed.deadFiles) || !Array.isArray(parsed.deadSymbols)) {
|
|
7096
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7097
|
+
res.end(
|
|
7098
|
+
JSON.stringify({
|
|
7099
|
+
error: "Invalid scan result: missing or malformed required fields (deadPackages, deadFiles, deadSymbols)"
|
|
7100
|
+
})
|
|
7101
|
+
);
|
|
7102
|
+
return;
|
|
7103
|
+
}
|
|
7104
|
+
const result = parsed;
|
|
7105
|
+
const plan = buildActionPlan(result);
|
|
7106
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7107
|
+
res.end(JSON.stringify(plan));
|
|
7108
|
+
}).catch((err) => {
|
|
7109
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
7110
|
+
res.end(
|
|
7111
|
+
JSON.stringify({
|
|
7112
|
+
error: "Failed to read request body",
|
|
7113
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
7114
|
+
})
|
|
7115
|
+
);
|
|
7116
|
+
});
|
|
7117
|
+
}
|
|
7118
|
+
function buildActionPlan(result) {
|
|
7119
|
+
const files = /* @__PURE__ */ new Map();
|
|
7120
|
+
for (const dp of result.deadPackages) {
|
|
7121
|
+
const pseudoFile = {
|
|
7122
|
+
file: `${dp.package}/ (package)`,
|
|
7123
|
+
symbolCount: dp.fileCount,
|
|
7124
|
+
symbols: [`remove package ${dp.package} (${dp.fileCount} files, path: ${dp.path})`],
|
|
7125
|
+
priority: 0
|
|
7126
|
+
};
|
|
7127
|
+
files.set(pseudoFile.file, pseudoFile);
|
|
7128
|
+
}
|
|
7129
|
+
for (const df of result.deadFiles) {
|
|
7130
|
+
const existing = files.get(df.file);
|
|
7131
|
+
if (existing) {
|
|
7132
|
+
if (existing.priority > 1) existing.priority = 1;
|
|
7133
|
+
existing.symbolCount += df.symbolCount;
|
|
7134
|
+
continue;
|
|
7135
|
+
}
|
|
7136
|
+
files.set(df.file, {
|
|
7137
|
+
file: df.file,
|
|
7138
|
+
symbolCount: df.symbolCount,
|
|
7139
|
+
symbols: [`entire file (${df.symbolCount} symbols) is dead`],
|
|
7140
|
+
priority: 1
|
|
7141
|
+
});
|
|
7142
|
+
}
|
|
7143
|
+
const deadInAliveFiles = /* @__PURE__ */ new Map();
|
|
7144
|
+
const deadFileSet = new Set(result.deadFiles.map((df) => df.file));
|
|
7145
|
+
for (const ds of result.deadSymbols) {
|
|
7146
|
+
if (deadFileSet.has(ds.file)) continue;
|
|
7147
|
+
const list = deadInAliveFiles.get(ds.file) ?? [];
|
|
7148
|
+
list.push(`${ds.kind} ${ds.name} (line ${ds.line})`);
|
|
7149
|
+
deadInAliveFiles.set(ds.file, list);
|
|
7150
|
+
}
|
|
7151
|
+
for (const [file, symbols] of deadInAliveFiles) {
|
|
7152
|
+
const existing = files.get(file);
|
|
7153
|
+
if (existing) {
|
|
7154
|
+
existing.symbols.push(...symbols);
|
|
7155
|
+
existing.symbolCount += symbols.length;
|
|
7156
|
+
continue;
|
|
7157
|
+
}
|
|
7158
|
+
files.set(file, {
|
|
7159
|
+
file,
|
|
7160
|
+
symbolCount: symbols.length,
|
|
7161
|
+
symbols,
|
|
7162
|
+
priority: 2
|
|
7163
|
+
});
|
|
7164
|
+
}
|
|
7165
|
+
const sorted = [...files.values()].sort((a, b) => a.priority - b.priority || a.file.localeCompare(b.file));
|
|
7166
|
+
return {
|
|
7167
|
+
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.`,
|
|
7168
|
+
files: sorted,
|
|
7169
|
+
totalDeadSymbols: result.stats.dead,
|
|
7170
|
+
totalDeadFiles: result.deadFiles.length,
|
|
7171
|
+
totalDeadPackages: result.deadPackages.length
|
|
7172
|
+
};
|
|
7173
|
+
}
|
|
7174
|
+
|
|
6779
7175
|
// src/server/http-server/api-handlers.ts
|
|
6780
7176
|
async function handleApiSessions(res, globalRoot) {
|
|
6781
7177
|
if (!globalRoot) {
|
|
@@ -6997,8 +7393,8 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
6997
7393
|
res.end(JSON.stringify({ error: String(err) }));
|
|
6998
7394
|
}
|
|
6999
7395
|
}
|
|
7000
|
-
function
|
|
7001
|
-
return new Promise((
|
|
7396
|
+
function readJsonBody2(req) {
|
|
7397
|
+
return new Promise((resolve16, reject) => {
|
|
7002
7398
|
let data = "";
|
|
7003
7399
|
req.on("data", (chunk) => {
|
|
7004
7400
|
data += chunk;
|
|
@@ -7009,7 +7405,7 @@ function readJsonBody(req) {
|
|
|
7009
7405
|
});
|
|
7010
7406
|
req.on("end", () => {
|
|
7011
7407
|
try {
|
|
7012
|
-
|
|
7408
|
+
resolve16(data ? JSON.parse(data) : {});
|
|
7013
7409
|
} catch (err) {
|
|
7014
7410
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
7015
7411
|
}
|
|
@@ -7025,7 +7421,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
7025
7421
|
}
|
|
7026
7422
|
let body;
|
|
7027
7423
|
try {
|
|
7028
|
-
body = await
|
|
7424
|
+
body = await readJsonBody2(req);
|
|
7029
7425
|
} catch {
|
|
7030
7426
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7031
7427
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7127,7 +7523,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
7127
7523
|
}
|
|
7128
7524
|
let body = {};
|
|
7129
7525
|
try {
|
|
7130
|
-
body = await
|
|
7526
|
+
body = await readJsonBody2(req);
|
|
7131
7527
|
} catch {
|
|
7132
7528
|
}
|
|
7133
7529
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
@@ -7168,7 +7564,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7168
7564
|
}
|
|
7169
7565
|
let body;
|
|
7170
7566
|
try {
|
|
7171
|
-
body = await
|
|
7567
|
+
body = await readJsonBody2(req);
|
|
7172
7568
|
} catch {
|
|
7173
7569
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7174
7570
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7232,12 +7628,12 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7232
7628
|
|
|
7233
7629
|
// src/server/projects-manifest.ts
|
|
7234
7630
|
import * as fs8 from "node:fs/promises";
|
|
7235
|
-
import * as
|
|
7631
|
+
import * as path11 from "node:path";
|
|
7236
7632
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7237
7633
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7238
7634
|
function projectsJsonPath(globalConfigPath) {
|
|
7239
|
-
const base =
|
|
7240
|
-
return
|
|
7635
|
+
const base = path11.dirname(globalConfigPath);
|
|
7636
|
+
return path11.join(base, "projects.json");
|
|
7241
7637
|
}
|
|
7242
7638
|
async function loadManifest(globalConfigPath) {
|
|
7243
7639
|
try {
|
|
@@ -7250,37 +7646,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7250
7646
|
}
|
|
7251
7647
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7252
7648
|
const file = projectsJsonPath(globalConfigPath);
|
|
7253
|
-
await fs8.mkdir(
|
|
7649
|
+
await fs8.mkdir(path11.dirname(file), { recursive: true });
|
|
7254
7650
|
await fs8.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7255
7651
|
}
|
|
7256
7652
|
function generateProjectSlug(rootPath) {
|
|
7257
7653
|
return projectSlug(rootPath);
|
|
7258
7654
|
}
|
|
7259
7655
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7260
|
-
const base =
|
|
7261
|
-
const dir =
|
|
7656
|
+
const base = path11.dirname(globalConfigPath);
|
|
7657
|
+
const dir = path11.join(base, "projects", slug);
|
|
7262
7658
|
await fs8.mkdir(dir, { recursive: true });
|
|
7263
7659
|
return dir;
|
|
7264
7660
|
}
|
|
7265
7661
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7266
|
-
const root =
|
|
7662
|
+
const root = path11.resolve(options.projectRoot);
|
|
7267
7663
|
const file = projectsJsonPath(globalConfigPath);
|
|
7268
7664
|
let entry;
|
|
7269
7665
|
await withFileLock(file, async () => {
|
|
7270
7666
|
const manifest = await loadManifest(globalConfigPath);
|
|
7271
7667
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7272
|
-
entry = manifest.projects.find((candidate) =>
|
|
7668
|
+
entry = manifest.projects.find((candidate) => path11.resolve(candidate.root) === root);
|
|
7273
7669
|
if (entry) {
|
|
7274
7670
|
entry.lastSeen = now;
|
|
7275
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7671
|
+
if (options.workingDir) entry.lastWorkingDir = path11.resolve(options.workingDir);
|
|
7276
7672
|
} else {
|
|
7277
7673
|
entry = {
|
|
7278
|
-
name: options.name ??
|
|
7674
|
+
name: options.name ?? path11.basename(root),
|
|
7279
7675
|
root,
|
|
7280
7676
|
slug: generateProjectSlug(root),
|
|
7281
7677
|
createdAt: now,
|
|
7282
7678
|
lastSeen: now,
|
|
7283
|
-
lastWorkingDir: options.workingDir ?
|
|
7679
|
+
lastWorkingDir: options.workingDir ? path11.resolve(options.workingDir) : void 0
|
|
7284
7680
|
};
|
|
7285
7681
|
manifest.projects.push(entry);
|
|
7286
7682
|
}
|
|
@@ -7691,9 +8087,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
7691
8087
|
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'`;
|
|
7692
8088
|
}
|
|
7693
8089
|
function isInsideDist(candidate, distDir) {
|
|
7694
|
-
const root =
|
|
7695
|
-
const resolved =
|
|
7696
|
-
return resolved === root || resolved.startsWith(root +
|
|
8090
|
+
const root = path12.resolve(distDir);
|
|
8091
|
+
const resolved = path12.resolve(candidate);
|
|
8092
|
+
return resolved === root || resolved.startsWith(root + path12.sep);
|
|
7697
8093
|
}
|
|
7698
8094
|
function decodeSessionId(segment) {
|
|
7699
8095
|
try {
|
|
@@ -7713,7 +8109,7 @@ function strictDecodeParam(segment, res) {
|
|
|
7713
8109
|
}
|
|
7714
8110
|
function createHttpServer(opts) {
|
|
7715
8111
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
7716
|
-
const distDir =
|
|
8112
|
+
const distDir = path12.resolve(opts.distDir);
|
|
7717
8113
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
7718
8114
|
let techStackRuntime = null;
|
|
7719
8115
|
const getTechStackRuntime = async () => {
|
|
@@ -7932,6 +8328,42 @@ function createHttpServer(opts) {
|
|
|
7932
8328
|
);
|
|
7933
8329
|
return;
|
|
7934
8330
|
}
|
|
8331
|
+
if (url.pathname === "/api/deadcode/scan" && req.method === "POST") {
|
|
8332
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8333
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8334
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8335
|
+
return;
|
|
8336
|
+
}
|
|
8337
|
+
if (!opts.projectRoot) {
|
|
8338
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8339
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8340
|
+
return;
|
|
8341
|
+
}
|
|
8342
|
+
const deadCodeDeps = {
|
|
8343
|
+
projectRoot: opts.projectRoot,
|
|
8344
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8345
|
+
};
|
|
8346
|
+
await handleDeadCodeScan(res, deadCodeDeps, req);
|
|
8347
|
+
return;
|
|
8348
|
+
}
|
|
8349
|
+
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
8350
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8351
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8352
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8353
|
+
return;
|
|
8354
|
+
}
|
|
8355
|
+
if (!opts.projectRoot) {
|
|
8356
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8357
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8358
|
+
return;
|
|
8359
|
+
}
|
|
8360
|
+
const deadCodeDeps = {
|
|
8361
|
+
projectRoot: opts.projectRoot,
|
|
8362
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8363
|
+
};
|
|
8364
|
+
await handleDeadCodeActionPlan(res, deadCodeDeps, req);
|
|
8365
|
+
return;
|
|
8366
|
+
}
|
|
7935
8367
|
if (url.pathname.startsWith("/api/techstack/")) {
|
|
7936
8368
|
if (requireAccessToken && !accessTokenOk) {
|
|
7937
8369
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -8064,17 +8496,17 @@ function createHttpServer(opts) {
|
|
|
8064
8496
|
}
|
|
8065
8497
|
let filePath;
|
|
8066
8498
|
if (url.pathname === "/" || url.pathname === "") {
|
|
8067
|
-
filePath =
|
|
8499
|
+
filePath = path12.join(distDir, "index.html");
|
|
8068
8500
|
} else {
|
|
8069
|
-
filePath =
|
|
8501
|
+
filePath = path12.join(distDir, url.pathname);
|
|
8070
8502
|
}
|
|
8071
|
-
const resolvedPath =
|
|
8503
|
+
const resolvedPath = path12.resolve(filePath);
|
|
8072
8504
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
8073
8505
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
8074
8506
|
res.end("Forbidden");
|
|
8075
8507
|
return;
|
|
8076
8508
|
}
|
|
8077
|
-
const ext =
|
|
8509
|
+
const ext = path12.extname(resolvedPath);
|
|
8078
8510
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
8079
8511
|
res.setHeader("Content-Type", contentType);
|
|
8080
8512
|
setStaticSecurityHeaders(res);
|
|
@@ -8098,7 +8530,7 @@ function createHttpServer(opts) {
|
|
|
8098
8530
|
} catch (err) {
|
|
8099
8531
|
if (err.code === "ENOENT") {
|
|
8100
8532
|
try {
|
|
8101
|
-
const html = await fs9.readFile(
|
|
8533
|
+
const html = await fs9.readFile(path12.join(distDir, "index.html"), "utf8");
|
|
8102
8534
|
setStaticSecurityHeaders(res);
|
|
8103
8535
|
res.writeHead(200, {
|
|
8104
8536
|
"Content-Type": "text/html",
|
|
@@ -8128,14 +8560,14 @@ function createHttpServer(opts) {
|
|
|
8128
8560
|
|
|
8129
8561
|
// src/server/instance-registry.ts
|
|
8130
8562
|
import * as os from "node:os";
|
|
8131
|
-
import * as
|
|
8563
|
+
import * as path13 from "node:path";
|
|
8132
8564
|
import * as fs10 from "node:fs/promises";
|
|
8133
8565
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8134
8566
|
function defaultBaseDir() {
|
|
8135
|
-
return
|
|
8567
|
+
return path13.join(os.homedir(), ".wrongstack");
|
|
8136
8568
|
}
|
|
8137
8569
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8138
|
-
return
|
|
8570
|
+
return path13.join(baseDir, "webui-instances.json");
|
|
8139
8571
|
}
|
|
8140
8572
|
function isPidAlive(pid) {
|
|
8141
8573
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -11610,16 +12042,16 @@ function getSurfaceDefaultPorts(surface) {
|
|
|
11610
12042
|
return { http: SURFACE_DEFAULT_PORTS[surface].http };
|
|
11611
12043
|
}
|
|
11612
12044
|
function isPortFree(host, port) {
|
|
11613
|
-
return new Promise((
|
|
12045
|
+
return new Promise((resolve16) => {
|
|
11614
12046
|
const srv = net.createServer();
|
|
11615
|
-
srv.once("error", () =>
|
|
12047
|
+
srv.once("error", () => resolve16(false));
|
|
11616
12048
|
srv.once("listening", () => {
|
|
11617
|
-
srv.close(() =>
|
|
12049
|
+
srv.close(() => resolve16(true));
|
|
11618
12050
|
});
|
|
11619
12051
|
try {
|
|
11620
12052
|
srv.listen(port, host);
|
|
11621
12053
|
} catch {
|
|
11622
|
-
|
|
12054
|
+
resolve16(false);
|
|
11623
12055
|
}
|
|
11624
12056
|
});
|
|
11625
12057
|
}
|
|
@@ -11644,10 +12076,10 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
11644
12076
|
import { spawn as spawn2 } from "node:child_process";
|
|
11645
12077
|
import { existsSync } from "node:fs";
|
|
11646
12078
|
import { findPackageJSON } from "node:module";
|
|
11647
|
-
import * as
|
|
12079
|
+
import * as path14 from "node:path";
|
|
11648
12080
|
function resolveDistDir(input) {
|
|
11649
12081
|
const options = typeof input === "string" ? { explicitDistDir: input } : input ?? {};
|
|
11650
|
-
if (options.explicitDistDir) return
|
|
12082
|
+
if (options.explicitDistDir) return path14.resolve(options.explicitDistDir);
|
|
11651
12083
|
const exists = options.exists ?? existsSync;
|
|
11652
12084
|
let packageTarget;
|
|
11653
12085
|
try {
|
|
@@ -11659,15 +12091,15 @@ function resolveDistDir(input) {
|
|
|
11659
12091
|
);
|
|
11660
12092
|
}
|
|
11661
12093
|
if (!packageTarget) return null;
|
|
11662
|
-
const distDir =
|
|
12094
|
+
const distDir = path14.basename(packageTarget) === "package.json" ? path14.join(path14.dirname(packageTarget), "dist") : path14.dirname(packageTarget);
|
|
11663
12095
|
if (options.exists === void 0 && options.resolvePackageJson) return distDir;
|
|
11664
|
-
return exists(
|
|
12096
|
+
return exists(path14.join(distDir, "index.html")) ? distDir : null;
|
|
11665
12097
|
}
|
|
11666
12098
|
async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
11667
12099
|
const exists = deps2.exists ?? existsSync;
|
|
11668
12100
|
if (explicitDistDir) {
|
|
11669
|
-
const resolved2 =
|
|
11670
|
-
return exists(
|
|
12101
|
+
const resolved2 = path14.resolve(explicitDistDir);
|
|
12102
|
+
return exists(path14.join(resolved2, "index.html")) ? resolved2 : null;
|
|
11671
12103
|
}
|
|
11672
12104
|
const resolveOptions = {
|
|
11673
12105
|
resolvePackageJson: deps2.resolvePackageJson,
|
|
@@ -11679,7 +12111,7 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
11679
12111
|
try {
|
|
11680
12112
|
const packageJson = deps2.resolvePackageJson ? deps2.resolvePackageJson("@wrongstack/webui/package.json") : findPackageJSON("@wrongstack/webui", import.meta.url);
|
|
11681
12113
|
if (!packageJson) throw new Error("not found");
|
|
11682
|
-
packageDir =
|
|
12114
|
+
packageDir = path14.dirname(packageJson);
|
|
11683
12115
|
} catch {
|
|
11684
12116
|
throw new Error(
|
|
11685
12117
|
"@wrongstack/webui package could not be resolved. Install workspace dependencies and rebuild the CLI."
|
|
@@ -11688,8 +12120,8 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
11688
12120
|
const findRoot = deps2.findWorkspaceRoot ?? ((pkgDir) => {
|
|
11689
12121
|
let dir = pkgDir;
|
|
11690
12122
|
for (let i = 0; i < 10; i++) {
|
|
11691
|
-
if (existsSync(
|
|
11692
|
-
const parent =
|
|
12123
|
+
if (existsSync(path14.join(dir, "pnpm-workspace.yaml"))) return dir;
|
|
12124
|
+
const parent = path14.dirname(dir);
|
|
11693
12125
|
if (parent === dir) return null;
|
|
11694
12126
|
dir = parent;
|
|
11695
12127
|
}
|
|
@@ -11748,7 +12180,7 @@ async function startStaticServe(opts, deps2 = {}) {
|
|
|
11748
12180
|
return { server, port: opts.httpPort };
|
|
11749
12181
|
}
|
|
11750
12182
|
function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
11751
|
-
return new Promise((
|
|
12183
|
+
return new Promise((resolve16, reject) => {
|
|
11752
12184
|
const child = spawn2("pnpm", ["--filter", workspace, "build"], {
|
|
11753
12185
|
cwd,
|
|
11754
12186
|
shell: process.platform === "win32",
|
|
@@ -11766,14 +12198,14 @@ function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
|
11766
12198
|
});
|
|
11767
12199
|
child.once("close", (code) => {
|
|
11768
12200
|
clearTimeout(timer);
|
|
11769
|
-
if (code === 0)
|
|
12201
|
+
if (code === 0) resolve16();
|
|
11770
12202
|
else reject(new Error(`pnpm build exited with code ${String(code)}`));
|
|
11771
12203
|
});
|
|
11772
12204
|
});
|
|
11773
12205
|
}
|
|
11774
12206
|
|
|
11775
12207
|
// src/server/embedded-lifecycle.ts
|
|
11776
|
-
import * as
|
|
12208
|
+
import * as path15 from "node:path";
|
|
11777
12209
|
|
|
11778
12210
|
// src/server/network-info.ts
|
|
11779
12211
|
import * as os2 from "node:os";
|
|
@@ -11839,7 +12271,7 @@ function registerWebuiInstance(p, deps2 = {}) {
|
|
|
11839
12271
|
httpPort: p.httpPort,
|
|
11840
12272
|
host: p.host,
|
|
11841
12273
|
projectRoot: p.projectRoot,
|
|
11842
|
-
projectName:
|
|
12274
|
+
projectName: path15.basename(p.projectRoot) || p.projectRoot,
|
|
11843
12275
|
startedAt: p.startedAt,
|
|
11844
12276
|
url: buildWebUIAccessUrl({
|
|
11845
12277
|
host: p.host,
|
|
@@ -12059,7 +12491,7 @@ ${text2}` : text2;
|
|
|
12059
12491
|
|
|
12060
12492
|
// src/server/client-presence.ts
|
|
12061
12493
|
import * as crypto2 from "node:crypto";
|
|
12062
|
-
import * as
|
|
12494
|
+
import * as path16 from "node:path";
|
|
12063
12495
|
import {
|
|
12064
12496
|
getSharedProjectMailbox as getSharedProjectMailbox2,
|
|
12065
12497
|
resolveProjectDir as resolveProjectDir2
|
|
@@ -12077,7 +12509,7 @@ function createWebuiClientPresence(deps2) {
|
|
|
12077
12509
|
if (!deps2.projectRoot) return null;
|
|
12078
12510
|
try {
|
|
12079
12511
|
const projectRoot = deps2.projectRoot;
|
|
12080
|
-
const projectName =
|
|
12512
|
+
const projectName = path16.basename(projectRoot);
|
|
12081
12513
|
const nextMailbox = getSharedProjectMailbox2(
|
|
12082
12514
|
resolveProjectDir2(projectRoot, wstackGlobalRoot()),
|
|
12083
12515
|
deps2.events,
|
|
@@ -13035,7 +13467,7 @@ function seedContextMeta(config, context) {
|
|
|
13035
13467
|
meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
|
|
13036
13468
|
meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
|
|
13037
13469
|
meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
|
|
13038
|
-
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] :
|
|
13470
|
+
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 15e3;
|
|
13039
13471
|
meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
|
|
13040
13472
|
meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
|
|
13041
13473
|
const cascade = autoReviewExt?.["cascadeOn"];
|
|
@@ -13055,7 +13487,7 @@ function seedContextMeta(config, context) {
|
|
|
13055
13487
|
|
|
13056
13488
|
// src/server/pref-helpers.ts
|
|
13057
13489
|
import * as fs12 from "node:fs/promises";
|
|
13058
|
-
import * as
|
|
13490
|
+
import * as path17 from "node:path";
|
|
13059
13491
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
13060
13492
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
13061
13493
|
var PREF_KEYS = [
|
|
@@ -13151,7 +13583,7 @@ function prefSnapshot(contextMeta) {
|
|
|
13151
13583
|
return snapshot;
|
|
13152
13584
|
}
|
|
13153
13585
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
13154
|
-
const globalRoot =
|
|
13586
|
+
const globalRoot = path17.dirname(filePath);
|
|
13155
13587
|
await backupConfigFile(filePath, { globalRoot });
|
|
13156
13588
|
let raw;
|
|
13157
13589
|
try {
|
|
@@ -13631,7 +14063,7 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
13631
14063
|
|
|
13632
14064
|
// src/server/embedded-host-adapters.ts
|
|
13633
14065
|
import * as fs15 from "node:fs/promises";
|
|
13634
|
-
import * as
|
|
14066
|
+
import * as path19 from "node:path";
|
|
13635
14067
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
13636
14068
|
import { DefaultSessionStore as DefaultSessionStore2 } from "@wrongstack/core/storage";
|
|
13637
14069
|
import { toErrorMessage as toErrorMessage7, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
|
|
@@ -13639,7 +14071,7 @@ import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
|
13639
14071
|
|
|
13640
14072
|
// src/server/project-handlers.ts
|
|
13641
14073
|
import * as fs13 from "node:fs/promises";
|
|
13642
|
-
import * as
|
|
14074
|
+
import * as path18 from "node:path";
|
|
13643
14075
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
13644
14076
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
13645
14077
|
function createProjectHandlers(ctx) {
|
|
@@ -13691,8 +14123,8 @@ function createProjectHandlers(ctx) {
|
|
|
13691
14123
|
});
|
|
13692
14124
|
return;
|
|
13693
14125
|
}
|
|
13694
|
-
const resolved =
|
|
13695
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14126
|
+
const resolved = path18.resolve(parsed.value.root);
|
|
14127
|
+
const name2 = parsed.value.name?.trim() || path18.basename(resolved);
|
|
13696
14128
|
try {
|
|
13697
14129
|
const stat3 = await fs13.stat(resolved).catch(() => null);
|
|
13698
14130
|
if (!stat3?.isDirectory()) {
|
|
@@ -13703,7 +14135,7 @@ function createProjectHandlers(ctx) {
|
|
|
13703
14135
|
return;
|
|
13704
14136
|
}
|
|
13705
14137
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
13706
|
-
const already = before.projects.some((project) =>
|
|
14138
|
+
const already = before.projects.some((project) => path18.resolve(project.root) === resolved);
|
|
13707
14139
|
const entry = await touchProjectInManifest(
|
|
13708
14140
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
13709
14141
|
ctx.globalConfigPath
|
|
@@ -13733,8 +14165,8 @@ function createProjectHandlers(ctx) {
|
|
|
13733
14165
|
});
|
|
13734
14166
|
return;
|
|
13735
14167
|
}
|
|
13736
|
-
const resolved =
|
|
13737
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14168
|
+
const resolved = path18.resolve(parsed.value.root);
|
|
14169
|
+
const name2 = parsed.value.name?.trim() || path18.basename(resolved);
|
|
13738
14170
|
if (!ctx.allowProjectMutations) {
|
|
13739
14171
|
sendTo(ws, {
|
|
13740
14172
|
type: "projects.selected",
|
|
@@ -13769,6 +14201,17 @@ function createProjectHandlers(ctx) {
|
|
|
13769
14201
|
});
|
|
13770
14202
|
const previous = ctx.getSession();
|
|
13771
14203
|
const previousId = previous.id;
|
|
14204
|
+
const previousProjectRoot = ctx.getProjectRoot();
|
|
14205
|
+
const previousPaths = resolveWstackPaths4({
|
|
14206
|
+
projectRoot: previousProjectRoot,
|
|
14207
|
+
globalRoot: ctx.wpaths.globalRoot
|
|
14208
|
+
});
|
|
14209
|
+
const previousIdentityTarget = {
|
|
14210
|
+
projectSlug: previousPaths.projectSlug,
|
|
14211
|
+
projectRoot: previousProjectRoot,
|
|
14212
|
+
projectName: path18.basename(previousProjectRoot),
|
|
14213
|
+
workingDir: ctx.context.workingDir
|
|
14214
|
+
};
|
|
13772
14215
|
const previousUsage = ctx.tokenCounter.total();
|
|
13773
14216
|
const config = ctx.getConfig?.() ?? ctx.config;
|
|
13774
14217
|
const next = await store.create({
|
|
@@ -13794,7 +14237,16 @@ function createProjectHandlers(ctx) {
|
|
|
13794
14237
|
};
|
|
13795
14238
|
try {
|
|
13796
14239
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
14240
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
13797
14241
|
} catch (err) {
|
|
14242
|
+
try {
|
|
14243
|
+
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
14244
|
+
} catch {
|
|
14245
|
+
}
|
|
14246
|
+
try {
|
|
14247
|
+
await ctx.onSessionSwapped?.(previous.id, previousIdentityTarget);
|
|
14248
|
+
} catch {
|
|
14249
|
+
}
|
|
13798
14250
|
await next.close().catch(() => void 0);
|
|
13799
14251
|
await store.delete(next.id).catch(() => void 0);
|
|
13800
14252
|
throw err;
|
|
@@ -14880,6 +15332,7 @@ var CLIENT_WORKSPACE_MESSAGE_TYPES = [
|
|
|
14880
15332
|
var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
|
|
14881
15333
|
"codebase.index.server.shutdown",
|
|
14882
15334
|
"connections.health",
|
|
15335
|
+
"connections.service_action",
|
|
14883
15336
|
"diag.get",
|
|
14884
15337
|
"key.add",
|
|
14885
15338
|
"key.delete",
|
|
@@ -15153,6 +15606,7 @@ var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
|
15153
15606
|
"codebase.index.server.shutdown_result",
|
|
15154
15607
|
"connections.health_error",
|
|
15155
15608
|
"connections.health_result",
|
|
15609
|
+
"connections.service_action_result",
|
|
15156
15610
|
"diag.get",
|
|
15157
15611
|
"key.operation_result",
|
|
15158
15612
|
"model.switch_result",
|
|
@@ -15197,13 +15651,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
15197
15651
|
// src/protocol/decoder.ts
|
|
15198
15652
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
15199
15653
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
15200
|
-
function inspectValue(value,
|
|
15654
|
+
function inspectValue(value, path34, depth) {
|
|
15201
15655
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
15202
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
15656
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path34 };
|
|
15203
15657
|
}
|
|
15204
15658
|
if (value === null || typeof value !== "object") return null;
|
|
15205
15659
|
for (const key of Object.keys(value)) {
|
|
15206
|
-
const childPath = `${
|
|
15660
|
+
const childPath = `${path34}.${key}`;
|
|
15207
15661
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
15208
15662
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
15209
15663
|
}
|
|
@@ -15344,7 +15798,8 @@ function projectToolMessage(message) {
|
|
|
15344
15798
|
name: text(payload["name"]),
|
|
15345
15799
|
ok: payload["ok"] !== false,
|
|
15346
15800
|
durationMs: finite(payload["durationMs"]),
|
|
15347
|
-
...typeof payload["output"] === "string" ? { output: payload["output"] } : {}
|
|
15801
|
+
...typeof payload["output"] === "string" ? { output: payload["output"] } : {},
|
|
15802
|
+
...Array.isArray(payload["sage"]) ? { sage: payload["sage"].filter((line) => typeof line === "string") } : {}
|
|
15348
15803
|
};
|
|
15349
15804
|
}
|
|
15350
15805
|
return null;
|
|
@@ -15584,6 +16039,7 @@ function createSessionHandlers(ctx) {
|
|
|
15584
16039
|
ctx.context.session = next;
|
|
15585
16040
|
ctx.context.state.replaceMessages(messages);
|
|
15586
16041
|
await ctx.context.flushConversationJournal?.();
|
|
16042
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, sessionsDirectory());
|
|
15587
16043
|
ctx.context.state.replaceTodos(todos);
|
|
15588
16044
|
resetContextAccounting();
|
|
15589
16045
|
ctx.context.readFiles.clear();
|
|
@@ -15980,7 +16436,10 @@ function createSessionHandlers(ctx) {
|
|
|
15980
16436
|
rollbackClaim = await ctx.claimSession?.(canonicalId);
|
|
15981
16437
|
const resumed = await store.resume(canonicalId);
|
|
15982
16438
|
const restoredTodos = await loadTodosCheckpoint(
|
|
15983
|
-
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json")
|
|
16439
|
+
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json"),
|
|
16440
|
+
ctx.events,
|
|
16441
|
+
ctx.context.traceId,
|
|
16442
|
+
resumed.writer.id
|
|
15984
16443
|
).catch(() => null) ?? [];
|
|
15985
16444
|
activated = true;
|
|
15986
16445
|
await activateSession(
|
|
@@ -16132,7 +16591,7 @@ function createEmbeddedConversationRoutes(ctx) {
|
|
|
16132
16591
|
}
|
|
16133
16592
|
function sessionStoreFor(opts) {
|
|
16134
16593
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
16135
|
-
return opts.sessionStore ?? new DefaultSessionStore2({ dir:
|
|
16594
|
+
return opts.sessionStore ?? new DefaultSessionStore2({ dir: path19.join(projectRoot, ".wrongstack", "sessions"), projectRoot });
|
|
16136
16595
|
}
|
|
16137
16596
|
function createEmbeddedSessionRoutes(ctx) {
|
|
16138
16597
|
const { opts } = ctx;
|
|
@@ -16142,6 +16601,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16142
16601
|
config: { model: actx.model ?? "", provider: actx.provider?.id ?? "" },
|
|
16143
16602
|
getConfig: () => ({ model: actx.model ?? "", provider: actx.provider?.id ?? "" }),
|
|
16144
16603
|
context: actx,
|
|
16604
|
+
events: opts.events,
|
|
16145
16605
|
listTools: () => opts.agent.tools.list(),
|
|
16146
16606
|
getCompactor: () => opts.agent.container.resolve(TOKENS.Compactor),
|
|
16147
16607
|
getCustomModeStore: ctx.getCustomModeStore,
|
|
@@ -16150,11 +16610,12 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16150
16610
|
getSession: () => actx.session ?? opts.session,
|
|
16151
16611
|
getSessionStore: () => sessionStoreFor(opts),
|
|
16152
16612
|
canSwapSessions: () => opts.sessionStore !== void 0,
|
|
16153
|
-
getSessionsDir: () => opts.sessionsDir ??
|
|
16613
|
+
getSessionsDir: () => opts.sessionsDir ?? path19.join(getProjectRoot(), ".wrongstack", "sessions"),
|
|
16154
16614
|
setSession: (next) => {
|
|
16155
16615
|
actx.session = next;
|
|
16156
16616
|
},
|
|
16157
16617
|
claimSession: opts.claimSession,
|
|
16618
|
+
onBeforeSessionTodosReplaced: async (sessionId, sessionsDir) => opts.onBeforeSessionTodosReplaced?.(sessionId, sessionsDir),
|
|
16158
16619
|
onSessionSwapped: async (sessionId, target) => opts.onSessionSwapped?.(sessionId, target),
|
|
16159
16620
|
abortActiveRun: ctx.abortActiveRun,
|
|
16160
16621
|
isRunActive: ctx.isRunActive,
|
|
@@ -16166,7 +16627,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16166
16627
|
async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
16167
16628
|
const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
16168
16629
|
try {
|
|
16169
|
-
const raw = await fs15.readFile(
|
|
16630
|
+
const raw = await fs15.readFile(path19.join(projectRoot, ".wrongstack", "goal.json"), "utf8");
|
|
16170
16631
|
ctx.broadcast({ type: "goal-state.updated", payload: JSON.parse(raw) });
|
|
16171
16632
|
} catch {
|
|
16172
16633
|
ctx.broadcast({ type: "goal-state.updated", payload: null });
|
|
@@ -16175,10 +16636,10 @@ async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
|
16175
16636
|
function createEmbeddedProjectRoutes(ctx) {
|
|
16176
16637
|
const { opts } = ctx;
|
|
16177
16638
|
const actx = opts.agent.ctx;
|
|
16178
|
-
const globalConfigPath = opts.globalConfigPath ??
|
|
16639
|
+
const globalConfigPath = opts.globalConfigPath ?? path19.join(wstackGlobalRoot2(), "config.json");
|
|
16179
16640
|
return createProjectHandlers({
|
|
16180
16641
|
globalConfigPath,
|
|
16181
|
-
wpaths: { globalRoot:
|
|
16642
|
+
wpaths: { globalRoot: path19.dirname(globalConfigPath) },
|
|
16182
16643
|
context: actx,
|
|
16183
16644
|
tokenCounter: actx.tokenCounter,
|
|
16184
16645
|
config: { model: actx.model, provider: actx.provider.id },
|
|
@@ -16203,6 +16664,7 @@ function createEmbeddedProjectRoutes(ctx) {
|
|
|
16203
16664
|
for (const controller of ctx.abortControllers.values()) controller.abort();
|
|
16204
16665
|
ctx.abortControllers.clear();
|
|
16205
16666
|
},
|
|
16667
|
+
onBeforeSessionTodosReplaced: async (sessionId, sessionsDir) => opts.onBeforeSessionTodosReplaced?.(sessionId, sessionsDir),
|
|
16206
16668
|
onSessionSwapped: async (sessionId, target) => opts.onSessionSwapped?.(sessionId, target),
|
|
16207
16669
|
allowProjectMutations: true,
|
|
16208
16670
|
sessionStartPayload: ctx.buildSessionStart,
|
|
@@ -16658,7 +17120,7 @@ ${String(p.content ?? "")}`;
|
|
|
16658
17120
|
};
|
|
16659
17121
|
|
|
16660
17122
|
// src/server/codebase-index-server-control.ts
|
|
16661
|
-
import { shutdownCodebaseIndexServer } from "@wrongstack/tools";
|
|
17123
|
+
import { shutdownCodebaseIndexServer as shutdownCodebaseIndexServer2 } from "@wrongstack/tools";
|
|
16662
17124
|
async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
16663
17125
|
if (message.type !== "codebase.index.server.shutdown") return false;
|
|
16664
17126
|
const requestId = message.payload && typeof message.payload === "object" && typeof message.payload.requestId === "string" ? message.payload.requestId : "";
|
|
@@ -16685,7 +17147,7 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
16685
17147
|
});
|
|
16686
17148
|
return true;
|
|
16687
17149
|
}
|
|
16688
|
-
const result = await
|
|
17150
|
+
const result = await shutdownCodebaseIndexServer2(
|
|
16689
17151
|
projectRoot,
|
|
16690
17152
|
deps2.getIndexDir(),
|
|
16691
17153
|
"websocket-request"
|
|
@@ -17226,7 +17688,7 @@ function createRouteFamilyDispatcher(options) {
|
|
|
17226
17688
|
|
|
17227
17689
|
// src/server/shell-open.ts
|
|
17228
17690
|
import * as fs16 from "node:fs/promises";
|
|
17229
|
-
import * as
|
|
17691
|
+
import * as path20 from "node:path";
|
|
17230
17692
|
import { spawn as spawn3 } from "node:child_process";
|
|
17231
17693
|
function normalizeShellOpenTarget(target) {
|
|
17232
17694
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -17237,11 +17699,11 @@ function shellQuote(s) {
|
|
|
17237
17699
|
}
|
|
17238
17700
|
async function handleShellOpen(req, logger, options) {
|
|
17239
17701
|
try {
|
|
17240
|
-
const resolved =
|
|
17702
|
+
const resolved = path20.resolve(req.path);
|
|
17241
17703
|
if (options?.projectRoot) {
|
|
17242
|
-
const root =
|
|
17243
|
-
const relative5 =
|
|
17244
|
-
const escapes = relative5.startsWith("..") ||
|
|
17704
|
+
const root = path20.resolve(options.projectRoot);
|
|
17705
|
+
const relative5 = path20.relative(root, resolved);
|
|
17706
|
+
const escapes = relative5.startsWith("..") || path20.isAbsolute(relative5);
|
|
17245
17707
|
if (escapes) {
|
|
17246
17708
|
return {
|
|
17247
17709
|
success: false,
|
|
@@ -17632,6 +18094,13 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
17632
18094
|
message
|
|
17633
18095
|
))
|
|
17634
18096
|
return;
|
|
18097
|
+
if (await handleConnectionsServiceAction(ws, message, {
|
|
18098
|
+
getProjectRoot: projectRoot,
|
|
18099
|
+
getIndexDir: () => typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
|
|
18100
|
+
send: send2,
|
|
18101
|
+
backend: "cli-embedded"
|
|
18102
|
+
}))
|
|
18103
|
+
return;
|
|
17635
18104
|
if (await handleCodebaseIndexServerControl(ws, message, {
|
|
17636
18105
|
trustBoundary: deps2.trustBoundary,
|
|
17637
18106
|
logger: deps2.logger,
|
|
@@ -17646,7 +18115,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
17646
18115
|
}
|
|
17647
18116
|
|
|
17648
18117
|
// src/server/provider-config-standalone.ts
|
|
17649
|
-
import * as
|
|
18118
|
+
import * as path21 from "node:path";
|
|
17650
18119
|
import { DefaultSecretVault } from "@wrongstack/core/security";
|
|
17651
18120
|
function createProviderConfigIO(configPath) {
|
|
17652
18121
|
const keyFile = vaultKeyFileForConfigPath(configPath);
|
|
@@ -17657,10 +18126,10 @@ function createProviderConfigIO(configPath) {
|
|
|
17657
18126
|
};
|
|
17658
18127
|
}
|
|
17659
18128
|
function vaultKeyFileForConfigPath(configPath) {
|
|
17660
|
-
const configDir =
|
|
17661
|
-
const parentDir =
|
|
17662
|
-
const globalRoot =
|
|
17663
|
-
return
|
|
18129
|
+
const configDir = path21.dirname(configPath);
|
|
18130
|
+
const parentDir = path21.dirname(configDir);
|
|
18131
|
+
const globalRoot = path21.basename(parentDir) === "profiles" ? path21.dirname(parentDir) : configDir;
|
|
18132
|
+
return path21.join(globalRoot, ".key");
|
|
17664
18133
|
}
|
|
17665
18134
|
|
|
17666
18135
|
// src/server/provider-store.ts
|
|
@@ -17677,8 +18146,8 @@ function createConfigWriteLock() {
|
|
|
17677
18146
|
acquire() {
|
|
17678
18147
|
const prev = lock;
|
|
17679
18148
|
let release = () => void 0;
|
|
17680
|
-
lock = new Promise((
|
|
17681
|
-
release =
|
|
18149
|
+
lock = new Promise((resolve16) => {
|
|
18150
|
+
release = resolve16;
|
|
17682
18151
|
});
|
|
17683
18152
|
return { prev, release };
|
|
17684
18153
|
}
|
|
@@ -17756,6 +18225,7 @@ function createProviderStore(deps2) {
|
|
|
17756
18225
|
import { listBoards as listBoards5 } from "@wrongstack/kanban";
|
|
17757
18226
|
import {
|
|
17758
18227
|
applySddLifecycle,
|
|
18228
|
+
extractVerificationCommand,
|
|
17759
18229
|
SddBoardStore
|
|
17760
18230
|
} from "@wrongstack/sdd";
|
|
17761
18231
|
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -17778,14 +18248,16 @@ var SddBoardWebSocketHandler = class {
|
|
|
17778
18248
|
store;
|
|
17779
18249
|
clients = /* @__PURE__ */ new Set();
|
|
17780
18250
|
lifecycle;
|
|
18251
|
+
security;
|
|
17781
18252
|
diskPollingEnabled;
|
|
17782
18253
|
latest = null;
|
|
17783
18254
|
poll = null;
|
|
17784
18255
|
pollInFlight = false;
|
|
17785
18256
|
unsub = null;
|
|
17786
|
-
constructor(boardsDir, events, lifecycle) {
|
|
18257
|
+
constructor(boardsDir, events, lifecycle, security) {
|
|
17787
18258
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
17788
18259
|
this.lifecycle = lifecycle;
|
|
18260
|
+
this.security = security;
|
|
17789
18261
|
this.diskPollingEnabled = events === void 0;
|
|
17790
18262
|
if (events) {
|
|
17791
18263
|
const handler = (e) => {
|
|
@@ -17831,6 +18303,43 @@ var SddBoardWebSocketHandler = class {
|
|
|
17831
18303
|
return;
|
|
17832
18304
|
}
|
|
17833
18305
|
if (CONTROL_TYPES.has(action)) {
|
|
18306
|
+
const verificationCommands = [];
|
|
18307
|
+
if (action === "set_task_verification") {
|
|
18308
|
+
const command = msg.payload?.verificationCommand;
|
|
18309
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192)) return;
|
|
18310
|
+
if (typeof command === "string" && command.trim()) {
|
|
18311
|
+
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
18312
|
+
}
|
|
18313
|
+
} else if (action === "split_task") {
|
|
18314
|
+
const subtasks = msg.payload?.subtasks;
|
|
18315
|
+
if (Array.isArray(subtasks)) {
|
|
18316
|
+
for (const subtask of subtasks) {
|
|
18317
|
+
if (!subtask || typeof subtask !== "object") continue;
|
|
18318
|
+
const criterion = subtask.successCriterion;
|
|
18319
|
+
if (criterion === void 0) continue;
|
|
18320
|
+
if (typeof criterion !== "string") return;
|
|
18321
|
+
const command = extractVerificationCommand([criterion]);
|
|
18322
|
+
if (!command) continue;
|
|
18323
|
+
if (command.length > 8192) return;
|
|
18324
|
+
verificationCommands.push({ command, operation: "sdd.split_task_verification" });
|
|
18325
|
+
}
|
|
18326
|
+
}
|
|
18327
|
+
}
|
|
18328
|
+
for (const { command, operation } of verificationCommands) {
|
|
18329
|
+
if (!this.security) return;
|
|
18330
|
+
const authorization = await authorizeWebUIAction(
|
|
18331
|
+
this.security.trustBoundary,
|
|
18332
|
+
{
|
|
18333
|
+
capability: "process.spawn",
|
|
18334
|
+
subject: { kind: "command", id: command },
|
|
18335
|
+
risk: "high",
|
|
18336
|
+
cwd: this.lifecycle?.projectRoot,
|
|
18337
|
+
metadata: { operation }
|
|
18338
|
+
},
|
|
18339
|
+
this.security.logger
|
|
18340
|
+
);
|
|
18341
|
+
if (!authorization.allowed) return;
|
|
18342
|
+
}
|
|
17834
18343
|
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
17835
18344
|
if (runId) {
|
|
17836
18345
|
await this.store.appendControl(runId, {
|
|
@@ -17943,7 +18452,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
17943
18452
|
};
|
|
17944
18453
|
|
|
17945
18454
|
// src/server/sdd-wizard-wiring.ts
|
|
17946
|
-
import * as
|
|
18455
|
+
import * as path22 from "node:path";
|
|
17947
18456
|
import {
|
|
17948
18457
|
DefaultTaskStore,
|
|
17949
18458
|
TaskTracker
|
|
@@ -18057,7 +18566,7 @@ function buildSddWizardDeps(opts) {
|
|
|
18057
18566
|
}).catch(() => {
|
|
18058
18567
|
projectContext = "";
|
|
18059
18568
|
});
|
|
18060
|
-
const sessionPath = opts.paths.projectSddSession ??
|
|
18569
|
+
const sessionPath = opts.paths.projectSddSession ?? path22.join(opts.paths.projectDir, "sdd-session.json");
|
|
18061
18570
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
18062
18571
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
18063
18572
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -18339,7 +18848,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
18339
18848
|
return;
|
|
18340
18849
|
}
|
|
18341
18850
|
const { runId } = await this.deps.startRun(this.driver, opts);
|
|
18342
|
-
this.driver.setLastRunId(runId);
|
|
18851
|
+
await this.driver.setLastRunId(runId);
|
|
18343
18852
|
if (this.driver.phase() !== "executing" && this.driver.phase() !== "done") {
|
|
18344
18853
|
try {
|
|
18345
18854
|
if (this.driver.phase() === "task_review") await this.driver.approve();
|
|
@@ -18398,7 +18907,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
18398
18907
|
this.lastAgentText = text2;
|
|
18399
18908
|
if (this.driver) {
|
|
18400
18909
|
await this.driver.ingestAgentOutput(text2);
|
|
18401
|
-
this.driver.setLastAgentText(text2);
|
|
18910
|
+
await this.driver.setLastAgentText(text2);
|
|
18402
18911
|
}
|
|
18403
18912
|
this.broadcast({ type: "sdd.spec.agent_text", payload: { text: text2 } });
|
|
18404
18913
|
} finally {
|
|
@@ -18429,10 +18938,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
18429
18938
|
|
|
18430
18939
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
18431
18940
|
import { watch as fsWatch } from "node:fs";
|
|
18432
|
-
import * as
|
|
18941
|
+
import * as path23 from "node:path";
|
|
18433
18942
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
18434
18943
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
18435
|
-
const globalRoot = globalConfigPath ?
|
|
18944
|
+
const globalRoot = globalConfigPath ? path23.dirname(globalConfigPath) : void 0;
|
|
18436
18945
|
if (!globalRoot) return void 0;
|
|
18437
18946
|
const disposers = [];
|
|
18438
18947
|
const broadcastSessions = async () => {
|
|
@@ -18442,8 +18951,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
18442
18951
|
const sessions = await registry.list();
|
|
18443
18952
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
18444
18953
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
18445
|
-
const myRoot =
|
|
18446
|
-
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug :
|
|
18954
|
+
const myRoot = path23.resolve(context.projectRoot);
|
|
18955
|
+
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path23.resolve(s.projectRoot) === myRoot).map((s) => ({
|
|
18447
18956
|
sessionId: s.sessionId,
|
|
18448
18957
|
projectName: s.projectName,
|
|
18449
18958
|
projectSlug: s.projectSlug,
|
|
@@ -18634,13 +19143,13 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
18634
19143
|
// src/server/setup-events-status-watcher.ts
|
|
18635
19144
|
import { watch as fsWatch2 } from "node:fs";
|
|
18636
19145
|
import * as fs18 from "node:fs/promises";
|
|
18637
|
-
import * as
|
|
19146
|
+
import * as path25 from "node:path";
|
|
18638
19147
|
|
|
18639
19148
|
// src/server/setup-events-watcher.ts
|
|
18640
|
-
import * as
|
|
19149
|
+
import * as path24 from "node:path";
|
|
18641
19150
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
18642
19151
|
const raw = String(filename);
|
|
18643
|
-
const relative5 =
|
|
19152
|
+
const relative5 = path24.isAbsolute(raw) ? path24.relative(projectsDir, raw) : raw;
|
|
18644
19153
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
18645
19154
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
18646
19155
|
return parts.at(-2) ?? null;
|
|
@@ -18675,7 +19184,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
18675
19184
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
18676
19185
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
18677
19186
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
18678
|
-
const projectsDir =
|
|
19187
|
+
const projectsDir = path25.join(wpaths.globalRoot, "projects");
|
|
18679
19188
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
18680
19189
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
18681
19190
|
const DEBOUNCE_MS2 = 150;
|
|
@@ -18734,7 +19243,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
18734
19243
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
18735
19244
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
18736
19245
|
try {
|
|
18737
|
-
const targetFile =
|
|
19246
|
+
const targetFile = path25.join(projectsDir, projectHash, "status.json");
|
|
18738
19247
|
const content = await fs18.readFile(targetFile, "utf-8");
|
|
18739
19248
|
const statusData = JSON.parse(content);
|
|
18740
19249
|
scheduleBroadcast(projectHash, statusData);
|
|
@@ -18793,7 +19302,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
18793
19302
|
|
|
18794
19303
|
// src/server/setup-events-core-watchers.ts
|
|
18795
19304
|
import * as fs19 from "node:fs/promises";
|
|
18796
|
-
import * as
|
|
19305
|
+
import * as path26 from "node:path";
|
|
18797
19306
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
18798
19307
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
18799
19308
|
const disposers = [];
|
|
@@ -18829,7 +19338,7 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
18829
19338
|
if (wpaths?.projectStatus) {
|
|
18830
19339
|
try {
|
|
18831
19340
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
18832
|
-
const dir =
|
|
19341
|
+
const dir = path26.dirname(statusFile);
|
|
18833
19342
|
await fs19.mkdir(dir, { recursive: true });
|
|
18834
19343
|
await fs19.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
18835
19344
|
} catch (err) {
|
|
@@ -19019,6 +19528,9 @@ function setupEvents(deps2) {
|
|
|
19019
19528
|
input: scrub(e.input),
|
|
19020
19529
|
fileTargets: extractCodeMapFileTargets(projectRoot || ".", e.name, e.input),
|
|
19021
19530
|
output: scrub(e.output),
|
|
19531
|
+
// SAGE-injected memory rides beside the tool text so the client renders
|
|
19532
|
+
// it as a memory card. Never folded back into `output`.
|
|
19533
|
+
...e.sage && e.sage.length > 0 ? { sage: e.sage.map((line) => scrub(line)) } : {},
|
|
19022
19534
|
outputBytes: e.outputBytes,
|
|
19023
19535
|
outputTokens: e.outputTokens,
|
|
19024
19536
|
outputLines: e.outputLines,
|
|
@@ -19853,17 +20365,24 @@ var SpecsWebSocketHandler = class {
|
|
|
19853
20365
|
// src/server/start-webui.ts
|
|
19854
20366
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
19855
20367
|
import * as http2 from "node:http";
|
|
19856
|
-
import * as
|
|
20368
|
+
import * as path33 from "node:path";
|
|
19857
20369
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
19858
20370
|
import { getSharedProjectMailbox as getSharedProjectMailbox5, resolveProjectDir as resolveProjectDir4 } from "@wrongstack/core/coordination";
|
|
19859
20371
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
19860
20372
|
import {
|
|
20373
|
+
attachTodosCheckpoint,
|
|
19861
20374
|
createSessionEventBridge,
|
|
19862
20375
|
resolveSessionLoggingConfig,
|
|
19863
20376
|
watchProviderConfig
|
|
19864
20377
|
} from "@wrongstack/core/storage";
|
|
19865
20378
|
import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } from "@wrongstack/core/types";
|
|
19866
|
-
import {
|
|
20379
|
+
import {
|
|
20380
|
+
expectDefined as expectDefined4,
|
|
20381
|
+
sessionScopedPath as sessionScopedPath3,
|
|
20382
|
+
startHeapWatchdog,
|
|
20383
|
+
toErrorMessage as toErrorMessage14,
|
|
20384
|
+
wstackGlobalRoot as wstackGlobalRoot4
|
|
20385
|
+
} from "@wrongstack/core/utils";
|
|
19867
20386
|
import { makeProviderFromConfig as makeProviderFromConfig5 } from "@wrongstack/providers";
|
|
19868
20387
|
import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
19869
20388
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
@@ -20050,7 +20569,7 @@ function findWorkspaceCliEntry(projectRoot) {
|
|
|
20050
20569
|
return null;
|
|
20051
20570
|
}
|
|
20052
20571
|
function sleep(ms) {
|
|
20053
|
-
return new Promise((
|
|
20572
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
20054
20573
|
}
|
|
20055
20574
|
|
|
20056
20575
|
// src/server/terminal-ws-handler.ts
|
|
@@ -20286,7 +20805,7 @@ function clampDim(value, fallback) {
|
|
|
20286
20805
|
}
|
|
20287
20806
|
|
|
20288
20807
|
// src/server/worktree-ws-handler.ts
|
|
20289
|
-
import { join as join14, resolve as
|
|
20808
|
+
import { join as join14, resolve as resolve13, sep as sep5 } from "node:path";
|
|
20290
20809
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
20291
20810
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
20292
20811
|
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
@@ -20347,13 +20866,13 @@ var WorktreeWebSocketHandler = class {
|
|
|
20347
20866
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
20348
20867
|
/** Absolute managed-worktrees root for this project. */
|
|
20349
20868
|
worktreesRoot() {
|
|
20350
|
-
return
|
|
20869
|
+
return resolve13(join14(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
20351
20870
|
}
|
|
20352
20871
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
20353
20872
|
underRoot(dir) {
|
|
20354
|
-
const abs =
|
|
20873
|
+
const abs = resolve13(dir);
|
|
20355
20874
|
const root = this.worktreesRoot();
|
|
20356
|
-
return abs !== root && abs.startsWith(root +
|
|
20875
|
+
return abs !== root && abs.startsWith(root + sep5);
|
|
20357
20876
|
}
|
|
20358
20877
|
/** Branches of worktrees a live in-session run currently owns. */
|
|
20359
20878
|
liveActiveBranches() {
|
|
@@ -20501,7 +21020,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
20501
21020
|
}
|
|
20502
21021
|
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
20503
21022
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
20504
|
-
const summary = await wt.diffSummary(
|
|
21023
|
+
const summary = await wt.diffSummary(resolve13(dir), base);
|
|
20505
21024
|
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
20506
21025
|
}
|
|
20507
21026
|
// ── internals ───────────────────────────────────────────────────────────
|
|
@@ -20650,7 +21169,9 @@ async function createAgentServices(input) {
|
|
|
20650
21169
|
memory: memoryRetrieval,
|
|
20651
21170
|
maxHintsPerTool: config.Sage?.inject?.maxHintsPerTool,
|
|
20652
21171
|
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
21172
|
+
taskAware: config.Sage?.inject?.taskAware,
|
|
20653
21173
|
minScore: config.Sage?.inject?.minScore,
|
|
21174
|
+
minImportance: config.Sage?.inject?.minImportance,
|
|
20654
21175
|
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
20655
21176
|
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
20656
21177
|
triggers: config.Sage?.inject?.triggers
|
|
@@ -20953,15 +21474,20 @@ async function createAgentServices(input) {
|
|
|
20953
21474
|
projectRoot
|
|
20954
21475
|
);
|
|
20955
21476
|
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
20956
|
-
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
|
|
21477
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
21478
|
+
wpaths.projectSddBoards,
|
|
21479
|
+
void 0,
|
|
21480
|
+
{
|
|
21481
|
+
projectRoot,
|
|
21482
|
+
paths: {
|
|
21483
|
+
projectSpecs: wpaths.projectSpecs,
|
|
21484
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
21485
|
+
projectSddSession: wpaths.projectSddSession,
|
|
21486
|
+
projectSddBoards: wpaths.projectSddBoards
|
|
21487
|
+
}
|
|
21488
|
+
},
|
|
21489
|
+
{ trustBoundary: input.trustBoundary, logger }
|
|
21490
|
+
);
|
|
20965
21491
|
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
20966
21492
|
buildSddWizardDeps({
|
|
20967
21493
|
agent,
|
|
@@ -20973,7 +21499,16 @@ async function createAgentServices(input) {
|
|
|
20973
21499
|
providerRegistry,
|
|
20974
21500
|
toolRegistry,
|
|
20975
21501
|
session: input.sessionGetter(),
|
|
20976
|
-
projectRoot
|
|
21502
|
+
projectRoot,
|
|
21503
|
+
// Thread the container-provided ProviderModelStatusTracker so a 429
|
|
21504
|
+
// from this subagent's first call transitions the (provider, model)
|
|
21505
|
+
// pair to `state: 'blocked'` instead of silently no-op'ing. The
|
|
21506
|
+
// runtime container binds a default `ProviderModelStatusTracker`
|
|
21507
|
+
// (see packages/runtime/src/container.ts); without this dep, the
|
|
21508
|
+
// subagent's fallback extension's tracker hooks are undefined and
|
|
21509
|
+
// round-robin keeps reassigning the doomed model. Mirrors the CLI
|
|
21510
|
+
// factory wiring at host-subagent-factory.ts:337.
|
|
21511
|
+
statusTracker: container.safeResolve(TOKENS2.ProviderModelStatusTracker)
|
|
20977
21512
|
}),
|
|
20978
21513
|
paths: {
|
|
20979
21514
|
projectSpecs: wpaths.projectSpecs,
|
|
@@ -21116,7 +21651,7 @@ function createConnectionHandler(options) {
|
|
|
21116
21651
|
}
|
|
21117
21652
|
|
|
21118
21653
|
// src/server/message-dispatcher.ts
|
|
21119
|
-
import
|
|
21654
|
+
import path27 from "node:path";
|
|
21120
21655
|
function createMessageDispatcher(opts) {
|
|
21121
21656
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
21122
21657
|
function makeWorklistContext() {
|
|
@@ -21137,7 +21672,7 @@ function createMessageDispatcher(opts) {
|
|
|
21137
21672
|
skillLoader: deps2.skillLoader,
|
|
21138
21673
|
skillInstaller: deps2.skillInstaller,
|
|
21139
21674
|
projectRoot,
|
|
21140
|
-
projectSkillsDir:
|
|
21675
|
+
projectSkillsDir: path27.join(projectRoot, ".wrongstack", "skills"),
|
|
21141
21676
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
21142
21677
|
};
|
|
21143
21678
|
}
|
|
@@ -21389,7 +21924,7 @@ function createMessageDispatcher(opts) {
|
|
|
21389
21924
|
|
|
21390
21925
|
// src/server/pre-context-services.ts
|
|
21391
21926
|
import { createRequire as createRequire3 } from "node:module";
|
|
21392
|
-
import * as
|
|
21927
|
+
import * as path30 from "node:path";
|
|
21393
21928
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
21394
21929
|
import {
|
|
21395
21930
|
getSharedProjectMailbox as getSharedProjectMailbox4,
|
|
@@ -21444,7 +21979,7 @@ import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/too
|
|
|
21444
21979
|
|
|
21445
21980
|
// src/server/model-auto-discovery.ts
|
|
21446
21981
|
import * as fs20 from "node:fs/promises";
|
|
21447
|
-
import * as
|
|
21982
|
+
import * as path28 from "node:path";
|
|
21448
21983
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
21449
21984
|
function isOverlayRegistry(value) {
|
|
21450
21985
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -21480,7 +22015,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
21480
22015
|
if (!isOverlayRegistry(registry)) return;
|
|
21481
22016
|
const targets = eligibleProviders(opts.config);
|
|
21482
22017
|
if (targets.length === 0) return;
|
|
21483
|
-
const cacheFile =
|
|
22018
|
+
const cacheFile = path28.join(opts.cacheDir, "discovered-models-cache.json");
|
|
21484
22019
|
const cache2 = await readCache(cacheFile);
|
|
21485
22020
|
let cacheDirty = false;
|
|
21486
22021
|
await Promise.all(
|
|
@@ -21517,7 +22052,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
21517
22052
|
);
|
|
21518
22053
|
if (cacheDirty) {
|
|
21519
22054
|
try {
|
|
21520
|
-
await fs20.mkdir(
|
|
22055
|
+
await fs20.mkdir(path28.dirname(cacheFile), { recursive: true });
|
|
21521
22056
|
await fs20.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
21522
22057
|
} catch {
|
|
21523
22058
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
@@ -21614,7 +22149,7 @@ function resolveSetupProvider(opts) {
|
|
|
21614
22149
|
}
|
|
21615
22150
|
|
|
21616
22151
|
// src/server/standalone-session-identity.ts
|
|
21617
|
-
import * as
|
|
22152
|
+
import * as path29 from "node:path";
|
|
21618
22153
|
import {
|
|
21619
22154
|
AgentStatusTracker,
|
|
21620
22155
|
FleetNotifier,
|
|
@@ -21633,7 +22168,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
21633
22168
|
let activeTarget = {
|
|
21634
22169
|
projectSlug: paths.projectSlug,
|
|
21635
22170
|
projectRoot: paths.projectRoot,
|
|
21636
|
-
projectName:
|
|
22171
|
+
projectName: path29.basename(paths.projectRoot),
|
|
21637
22172
|
workingDir: opts.workingDir
|
|
21638
22173
|
};
|
|
21639
22174
|
let pendingClaim;
|
|
@@ -21864,7 +22399,7 @@ async function createPreContextServices(input) {
|
|
|
21864
22399
|
await discoverAndMergeWebuiProviders({
|
|
21865
22400
|
config,
|
|
21866
22401
|
registry: modelsRegistry,
|
|
21867
|
-
cacheDir:
|
|
22402
|
+
cacheDir: path30.dirname(wpaths.modelsCache),
|
|
21868
22403
|
logger
|
|
21869
22404
|
});
|
|
21870
22405
|
} catch (err) {
|
|
@@ -21916,7 +22451,7 @@ async function createPreContextServices(input) {
|
|
|
21916
22451
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
21917
22452
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
21918
22453
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
21919
|
-
|
|
22454
|
+
path30.join(wpaths.projectDir, "mcp-auth.json"),
|
|
21920
22455
|
vault
|
|
21921
22456
|
);
|
|
21922
22457
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -22022,7 +22557,7 @@ async function createPreContextServices(input) {
|
|
|
22022
22557
|
};
|
|
22023
22558
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
22024
22559
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
22025
|
-
manifestPath:
|
|
22560
|
+
manifestPath: path30.join(wpaths.configDir, "installed-skills.json"),
|
|
22026
22561
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
22027
22562
|
globalSkillsDir: wpaths.globalSkills,
|
|
22028
22563
|
projectHash: wpaths.projectHash,
|
|
@@ -22032,8 +22567,8 @@ async function createPreContextServices(input) {
|
|
|
22032
22567
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
22033
22568
|
try {
|
|
22034
22569
|
const req = createRequire3(import.meta.url);
|
|
22035
|
-
return
|
|
22036
|
-
|
|
22570
|
+
return path30.join(
|
|
22571
|
+
path30.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
22037
22572
|
"data",
|
|
22038
22573
|
"prompts"
|
|
22039
22574
|
);
|
|
@@ -22137,7 +22672,7 @@ async function createPreContextServices(input) {
|
|
|
22137
22672
|
}
|
|
22138
22673
|
|
|
22139
22674
|
// src/server/routes.ts
|
|
22140
|
-
import
|
|
22675
|
+
import path31 from "node:path";
|
|
22141
22676
|
import { makeProviderFromConfig as makeProviderFromConfig4, withCatalogCapabilities } from "@wrongstack/providers";
|
|
22142
22677
|
|
|
22143
22678
|
// src/server/mode-handlers.ts
|
|
@@ -22262,6 +22797,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22262
22797
|
config: state.getConfig(),
|
|
22263
22798
|
clients: state.getClients(),
|
|
22264
22799
|
context: deps2.context,
|
|
22800
|
+
events: deps2.events,
|
|
22265
22801
|
toolRegistry: deps2.toolRegistry,
|
|
22266
22802
|
compactor: deps2.compactor,
|
|
22267
22803
|
customModeStore: deps2.customModeStore,
|
|
@@ -22273,6 +22809,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22273
22809
|
setSession: state.setSession,
|
|
22274
22810
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
22275
22811
|
claimSession: cb.claimSession,
|
|
22812
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
22276
22813
|
onSessionSwapped: cb.onSessionSwapped,
|
|
22277
22814
|
abortActiveRun: state.abortRunLock,
|
|
22278
22815
|
isRunActive: state.isRunActive,
|
|
@@ -22294,6 +22831,8 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22294
22831
|
setSessionStore: state.setSessionStore,
|
|
22295
22832
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
22296
22833
|
abortRunLock: state.abortRunLock,
|
|
22834
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
22835
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
22297
22836
|
sessionStartPayload: cb.sessionStartPayload
|
|
22298
22837
|
});
|
|
22299
22838
|
const modeRoutes = createModeHandlers({
|
|
@@ -22425,7 +22964,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22425
22964
|
};
|
|
22426
22965
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
22427
22966
|
getProjectRoot: state.getProjectRoot,
|
|
22428
|
-
getGlobalRoot: () =>
|
|
22967
|
+
getGlobalRoot: () => path31.dirname(deps2.globalConfigPath),
|
|
22429
22968
|
events: deps2.events
|
|
22430
22969
|
});
|
|
22431
22970
|
const mcpRoutes = {
|
|
@@ -22498,7 +23037,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22498
23037
|
}
|
|
22499
23038
|
|
|
22500
23039
|
// src/server/server-runtime.ts
|
|
22501
|
-
import * as
|
|
23040
|
+
import * as path32 from "node:path";
|
|
22502
23041
|
import { createRequire as createRequire4 } from "node:module";
|
|
22503
23042
|
import { fileURLToPath } from "node:url";
|
|
22504
23043
|
import { WebSocketServer } from "ws";
|
|
@@ -22559,7 +23098,7 @@ function createSessionStartPayload(g) {
|
|
|
22559
23098
|
inputCost,
|
|
22560
23099
|
outputCost,
|
|
22561
23100
|
cacheReadCost,
|
|
22562
|
-
projectName:
|
|
23101
|
+
projectName: path32.basename(projectRoot) || projectRoot,
|
|
22563
23102
|
projectRoot,
|
|
22564
23103
|
cwd: g.getWorkingDir(),
|
|
22565
23104
|
mode: g.getModeId(),
|
|
@@ -22647,13 +23186,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
22647
23186
|
};
|
|
22648
23187
|
}
|
|
22649
23188
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
22650
|
-
if (explicitDistDir) return
|
|
23189
|
+
if (explicitDistDir) return path32.resolve(explicitDistDir);
|
|
22651
23190
|
try {
|
|
22652
23191
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
22653
23192
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
22654
|
-
return
|
|
23193
|
+
return path32.dirname(serverEntry);
|
|
22655
23194
|
} catch {
|
|
22656
|
-
return
|
|
23195
|
+
return path32.resolve(path32.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
22657
23196
|
}
|
|
22658
23197
|
}
|
|
22659
23198
|
function startHttpServer(opts) {
|
|
@@ -22684,6 +23223,56 @@ function registerShutdown(deps2) {
|
|
|
22684
23223
|
}
|
|
22685
23224
|
|
|
22686
23225
|
// src/server/start-webui.ts
|
|
23226
|
+
function createStandaloneTodosCheckpointLifecycle(input) {
|
|
23227
|
+
let checkpointSessionId = input.sessionId;
|
|
23228
|
+
let checkpointSessionsDir = input.sessionsDir;
|
|
23229
|
+
const attachCheckpoint = (sessionId, sessionsDir) => attachTodosCheckpoint(
|
|
23230
|
+
input.state,
|
|
23231
|
+
sessionScopedPath3(sessionsDir, sessionId, ".todos.json"),
|
|
23232
|
+
sessionId,
|
|
23233
|
+
input.events,
|
|
23234
|
+
input.traceId,
|
|
23235
|
+
input.warn
|
|
23236
|
+
);
|
|
23237
|
+
let detachCurrent = attachCheckpoint(input.sessionId, input.sessionsDir);
|
|
23238
|
+
let checkpointAttached = true;
|
|
23239
|
+
const detachCurrentCheckpoint = async () => {
|
|
23240
|
+
if (!checkpointAttached) return;
|
|
23241
|
+
checkpointAttached = false;
|
|
23242
|
+
await detachCurrent();
|
|
23243
|
+
};
|
|
23244
|
+
let transitionTail = Promise.resolve();
|
|
23245
|
+
const rebind = (nextSessionId, sessionsDir) => {
|
|
23246
|
+
const transition = transitionTail.then(async () => {
|
|
23247
|
+
if (checkpointAttached && nextSessionId === checkpointSessionId && sessionsDir === checkpointSessionsDir) {
|
|
23248
|
+
return;
|
|
23249
|
+
}
|
|
23250
|
+
let detachFailed = false;
|
|
23251
|
+
let detachError;
|
|
23252
|
+
try {
|
|
23253
|
+
await detachCurrentCheckpoint();
|
|
23254
|
+
} catch (error2) {
|
|
23255
|
+
detachFailed = true;
|
|
23256
|
+
detachError = error2;
|
|
23257
|
+
}
|
|
23258
|
+
const nextDetach = attachCheckpoint(nextSessionId, sessionsDir);
|
|
23259
|
+
checkpointSessionId = nextSessionId;
|
|
23260
|
+
checkpointSessionsDir = sessionsDir;
|
|
23261
|
+
detachCurrent = nextDetach;
|
|
23262
|
+
checkpointAttached = true;
|
|
23263
|
+
if (detachFailed) throw detachError;
|
|
23264
|
+
});
|
|
23265
|
+
transitionTail = transition.catch(() => void 0);
|
|
23266
|
+
return transition;
|
|
23267
|
+
};
|
|
23268
|
+
return {
|
|
23269
|
+
rebind,
|
|
23270
|
+
detach: async () => {
|
|
23271
|
+
await transitionTail;
|
|
23272
|
+
await detachCurrentCheckpoint();
|
|
23273
|
+
}
|
|
23274
|
+
};
|
|
23275
|
+
}
|
|
22687
23276
|
async function startWebUI(opts = {}) {
|
|
22688
23277
|
ensureSessionShell();
|
|
22689
23278
|
const ports = await resolvePorts(opts);
|
|
@@ -22751,6 +23340,14 @@ async function startWebUI(opts = {}) {
|
|
|
22751
23340
|
} = preContext;
|
|
22752
23341
|
let sessionStore = preContext.sessionStore;
|
|
22753
23342
|
let session = preContext.session;
|
|
23343
|
+
const todosCheckpoint = createStandaloneTodosCheckpointLifecycle({
|
|
23344
|
+
state: context.state,
|
|
23345
|
+
sessionsDir: wpaths.projectSessions,
|
|
23346
|
+
sessionId: session.id,
|
|
23347
|
+
events,
|
|
23348
|
+
traceId: context.traceId,
|
|
23349
|
+
warn: (message) => logger.warn(message)
|
|
23350
|
+
});
|
|
22754
23351
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
22755
23352
|
let modeId = preContext.modeId;
|
|
22756
23353
|
const needsSetup = preContext.needsSetup;
|
|
@@ -22877,7 +23474,7 @@ async function startWebUI(opts = {}) {
|
|
|
22877
23474
|
if (events.listenerCount("tool.confirm_needed") === 0) {
|
|
22878
23475
|
throw new Error("No permission confirmation surface is connected");
|
|
22879
23476
|
}
|
|
22880
|
-
const decision = await new Promise((
|
|
23477
|
+
const decision = await new Promise((resolve16) => {
|
|
22881
23478
|
events.emit("tool.confirm_needed", {
|
|
22882
23479
|
sessionId: context.session.id,
|
|
22883
23480
|
tool: confirmTool,
|
|
@@ -22887,7 +23484,7 @@ async function startWebUI(opts = {}) {
|
|
|
22887
23484
|
decisionSource: pending.decisionSource,
|
|
22888
23485
|
riskTier: pending.riskTier,
|
|
22889
23486
|
boundaryReason: pending.boundaryReason,
|
|
22890
|
-
resolve:
|
|
23487
|
+
resolve: resolve16
|
|
22891
23488
|
});
|
|
22892
23489
|
});
|
|
22893
23490
|
const rule = { tool: "language_package", pattern: pending.suggestedPattern };
|
|
@@ -22987,21 +23584,21 @@ async function startWebUI(opts = {}) {
|
|
|
22987
23584
|
});
|
|
22988
23585
|
}
|
|
22989
23586
|
async function touchProjectEntry(root, workDir) {
|
|
22990
|
-
const resolved =
|
|
23587
|
+
const resolved = path33.resolve(root);
|
|
22991
23588
|
const manifest = await loadManifest(globalConfigPath);
|
|
22992
23589
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22993
|
-
const existing = manifest.projects.find((p) =>
|
|
23590
|
+
const existing = manifest.projects.find((p) => path33.resolve(p.root) === resolved);
|
|
22994
23591
|
if (existing) {
|
|
22995
23592
|
existing.lastSeen = now;
|
|
22996
|
-
if (workDir) existing.lastWorkingDir =
|
|
23593
|
+
if (workDir) existing.lastWorkingDir = path33.resolve(workDir);
|
|
22997
23594
|
} else {
|
|
22998
23595
|
manifest.projects.push({
|
|
22999
|
-
name:
|
|
23596
|
+
name: path33.basename(resolved),
|
|
23000
23597
|
root: resolved,
|
|
23001
23598
|
slug: generateProjectSlug(resolved),
|
|
23002
23599
|
createdAt: now,
|
|
23003
23600
|
lastSeen: now,
|
|
23004
|
-
lastWorkingDir: workDir ?
|
|
23601
|
+
lastWorkingDir: workDir ? path33.resolve(workDir) : void 0
|
|
23005
23602
|
});
|
|
23006
23603
|
}
|
|
23007
23604
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -23102,6 +23699,7 @@ async function startWebUI(opts = {}) {
|
|
|
23102
23699
|
const cb = {
|
|
23103
23700
|
sessionStartPayload,
|
|
23104
23701
|
claimSession: (sessionId, target) => sessionIdentity.claim(sessionId, target),
|
|
23702
|
+
onBeforeSessionTodosReplaced: todosCheckpoint.rebind,
|
|
23105
23703
|
onSessionSwapped: async (sessionId, target) => {
|
|
23106
23704
|
await sessionIdentity.activate(sessionId, target);
|
|
23107
23705
|
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
@@ -23252,6 +23850,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23252
23850
|
...wssSecondary ? [wssSecondary] : []
|
|
23253
23851
|
],
|
|
23254
23852
|
onShutdown: async () => {
|
|
23853
|
+
await todosCheckpoint.detach();
|
|
23255
23854
|
await stopHeapWatchdog();
|
|
23256
23855
|
credentialWatcherClose?.();
|
|
23257
23856
|
brainMonitor.stop();
|
|
@@ -23277,7 +23876,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23277
23876
|
await memoryStore.dispose().catch(
|
|
23278
23877
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage14(err)}`)
|
|
23279
23878
|
);
|
|
23280
|
-
await unregisterInstance(process.pid,
|
|
23879
|
+
await unregisterInstance(process.pid, path33.dirname(globalConfigPath));
|
|
23281
23880
|
}
|
|
23282
23881
|
});
|
|
23283
23882
|
}
|