@wrongstack/webui-server 0.296.2 → 0.296.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +851 -238
- package/dist/index.js.map +4 -4
- package/dist/protocol/client-workspace.d.ts +1 -1
- package/dist/protocol/client-workspace.d.ts.map +1 -1
- package/dist/protocol/index.js +4 -1
- package/dist/protocol/index.js.map +2 -2
- package/dist/protocol/projections.d.ts +6 -0
- package/dist/protocol/projections.d.ts.map +1 -1
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/registry.d.ts.map +1 -1
- package/dist/protocol/server-workspace.d.ts +1 -1
- package/dist/protocol/server-workspace.d.ts.map +1 -1
- package/dist/server/backend-services.d.ts.map +1 -1
- package/dist/server/connections-health-route.d.ts +8 -0
- package/dist/server/connections-health-route.d.ts.map +1 -1
- package/dist/server/embedded-host-adapters.d.ts +3 -0
- package/dist/server/embedded-host-adapters.d.ts.map +1 -1
- package/dist/server/embedded-message-router.d.ts.map +1 -1
- package/dist/server/entry.js +573 -209
- package/dist/server/entry.js.map +4 -4
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/project-handlers.d.ts +1 -0
- package/dist/server/project-handlers.d.ts.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/sdd-board-ws-handler.d.ts +8 -1
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -1
- package/dist/server/session-handlers.d.ts +3 -0
- package/dist/server/session-handlers.d.ts.map +1 -1
- package/dist/server/setup-events.d.ts.map +1 -1
- package/dist/server/start-webui.d.ts +12 -0
- package/dist/server/start-webui.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/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,12 @@ 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
|
+
resolveProjectIndexDaemonAvailability,
|
|
4963
|
+
shutdownCodebaseIndexServer
|
|
4964
|
+
} from "@wrongstack/tools";
|
|
4956
4965
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
4957
4966
|
if (message.type !== "connections.health") return false;
|
|
4958
4967
|
try {
|
|
@@ -5043,6 +5052,19 @@ async function chronicleHealth(projectRoot) {
|
|
|
5043
5052
|
}
|
|
5044
5053
|
async function codebaseIndexHealth(projectRoot, indexDir) {
|
|
5045
5054
|
const startedAt = Date.now();
|
|
5055
|
+
const availability = resolveProjectIndexDaemonAvailability(projectRoot, indexDir);
|
|
5056
|
+
if (availability.kind === "endpoint-invalid") {
|
|
5057
|
+
return {
|
|
5058
|
+
id: "codebase-index",
|
|
5059
|
+
label: "Codebase index",
|
|
5060
|
+
status: "unavailable",
|
|
5061
|
+
required: false,
|
|
5062
|
+
mode: "endpoint-invalid",
|
|
5063
|
+
detail: `Socket path is ${availability.byteLength} bytes \u2014 over this platform's ${availability.maxBytes}-byte sun_path limit. Queries fall back to a process-local index. Set a shorter TMPDIR to restore the shared daemon.`,
|
|
5064
|
+
endpoint: availability.endpoint,
|
|
5065
|
+
latencyMs: Date.now() - startedAt
|
|
5066
|
+
};
|
|
5067
|
+
}
|
|
5046
5068
|
try {
|
|
5047
5069
|
const health = await checkCodebaseIndexServerHealth(projectRoot, indexDir, {
|
|
5048
5070
|
timeoutMs: 2e3
|
|
@@ -5269,6 +5291,244 @@ async function mailboxHealth(projectRoot) {
|
|
|
5269
5291
|
connection.close();
|
|
5270
5292
|
}
|
|
5271
5293
|
}
|
|
5294
|
+
async function handleConnectionsServiceAction(ws, message, context) {
|
|
5295
|
+
if (message.type !== "connections.service_action") return false;
|
|
5296
|
+
const payload = message.payload;
|
|
5297
|
+
const serviceId = payload?.serviceId;
|
|
5298
|
+
const rawAction = payload?.action ?? "shutdown";
|
|
5299
|
+
if (!serviceId) {
|
|
5300
|
+
context.send(ws, {
|
|
5301
|
+
type: "connections.service_action_result",
|
|
5302
|
+
payload: {
|
|
5303
|
+
serviceId: null,
|
|
5304
|
+
action: rawAction,
|
|
5305
|
+
success: false,
|
|
5306
|
+
message: "Missing serviceId in payload"
|
|
5307
|
+
}
|
|
5308
|
+
});
|
|
5309
|
+
return true;
|
|
5310
|
+
}
|
|
5311
|
+
if (rawAction !== "shutdown") {
|
|
5312
|
+
context.send(ws, {
|
|
5313
|
+
type: "connections.service_action_result",
|
|
5314
|
+
payload: {
|
|
5315
|
+
serviceId,
|
|
5316
|
+
action: rawAction,
|
|
5317
|
+
success: false,
|
|
5318
|
+
message: `Unsupported action "${rawAction}" \u2014 only "shutdown" is currently supported`
|
|
5319
|
+
}
|
|
5320
|
+
});
|
|
5321
|
+
return true;
|
|
5322
|
+
}
|
|
5323
|
+
const action = rawAction;
|
|
5324
|
+
if (serviceId === "webui") {
|
|
5325
|
+
context.send(ws, {
|
|
5326
|
+
type: "connections.service_action_result",
|
|
5327
|
+
payload: {
|
|
5328
|
+
serviceId: "webui",
|
|
5329
|
+
action,
|
|
5330
|
+
success: false,
|
|
5331
|
+
message: "Cannot shut down the WebUI transport itself"
|
|
5332
|
+
}
|
|
5333
|
+
});
|
|
5334
|
+
return true;
|
|
5335
|
+
}
|
|
5336
|
+
try {
|
|
5337
|
+
const result = await executeServiceAction(
|
|
5338
|
+
serviceId,
|
|
5339
|
+
action,
|
|
5340
|
+
context.getProjectRoot(),
|
|
5341
|
+
context.getIndexDir()
|
|
5342
|
+
);
|
|
5343
|
+
context.send(ws, {
|
|
5344
|
+
type: "connections.service_action_result",
|
|
5345
|
+
payload: result
|
|
5346
|
+
});
|
|
5347
|
+
} catch (error2) {
|
|
5348
|
+
context.send(ws, {
|
|
5349
|
+
type: "connections.service_action_result",
|
|
5350
|
+
payload: {
|
|
5351
|
+
serviceId,
|
|
5352
|
+
action,
|
|
5353
|
+
success: false,
|
|
5354
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5355
|
+
}
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
return true;
|
|
5359
|
+
}
|
|
5360
|
+
async function executeServiceAction(serviceId, action, projectRoot, indexDir) {
|
|
5361
|
+
switch (serviceId) {
|
|
5362
|
+
case "kanban":
|
|
5363
|
+
return killKanbanServer(projectRoot, action);
|
|
5364
|
+
case "sage":
|
|
5365
|
+
return killSageServer(projectRoot, action);
|
|
5366
|
+
case "chronicle":
|
|
5367
|
+
return killChronicleServer(projectRoot, action);
|
|
5368
|
+
case "codebase-index":
|
|
5369
|
+
return killCodebaseIndexServer(projectRoot, indexDir, action);
|
|
5370
|
+
case "mailbox":
|
|
5371
|
+
return killMailboxServer(projectRoot, action);
|
|
5372
|
+
default:
|
|
5373
|
+
return {
|
|
5374
|
+
serviceId,
|
|
5375
|
+
action,
|
|
5376
|
+
success: false,
|
|
5377
|
+
message: `Unknown service: ${serviceId}`
|
|
5378
|
+
};
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
async function killKanbanServer(projectRoot, action) {
|
|
5382
|
+
if (process.env["WRONGSTACK_KANBAN_SERVER"] === "0") {
|
|
5383
|
+
return {
|
|
5384
|
+
serviceId: "kanban",
|
|
5385
|
+
action,
|
|
5386
|
+
success: false,
|
|
5387
|
+
message: "Kanban IPC daemon is disabled via WRONGSTACK_KANBAN_SERVER=0"
|
|
5388
|
+
};
|
|
5389
|
+
}
|
|
5390
|
+
let connection;
|
|
5391
|
+
try {
|
|
5392
|
+
connection = await getKanbanServerConnection(projectRoot);
|
|
5393
|
+
} catch (error2) {
|
|
5394
|
+
return {
|
|
5395
|
+
serviceId: "kanban",
|
|
5396
|
+
action,
|
|
5397
|
+
success: false,
|
|
5398
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5399
|
+
};
|
|
5400
|
+
}
|
|
5401
|
+
if (!connection) {
|
|
5402
|
+
return {
|
|
5403
|
+
serviceId: "kanban",
|
|
5404
|
+
action,
|
|
5405
|
+
success: false,
|
|
5406
|
+
message: "Kanban IPC daemon is not running"
|
|
5407
|
+
};
|
|
5408
|
+
}
|
|
5409
|
+
try {
|
|
5410
|
+
const result = await connection.request("shutdown", {
|
|
5411
|
+
reason: `WebUI request: ${action}`
|
|
5412
|
+
});
|
|
5413
|
+
return {
|
|
5414
|
+
serviceId: "kanban",
|
|
5415
|
+
action,
|
|
5416
|
+
success: result.stopping,
|
|
5417
|
+
message: result.stopping ? `Kanban IPC daemon ${action} requested` : `Kanban IPC daemon ${action} failed (shutdown not confirmed)`
|
|
5418
|
+
};
|
|
5419
|
+
} catch (error2) {
|
|
5420
|
+
return {
|
|
5421
|
+
serviceId: "kanban",
|
|
5422
|
+
action,
|
|
5423
|
+
success: false,
|
|
5424
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5425
|
+
};
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
async function killSageServer(projectRoot, action) {
|
|
5429
|
+
if (!isSageProjectServerAvailable()) {
|
|
5430
|
+
return {
|
|
5431
|
+
serviceId: "sage",
|
|
5432
|
+
action,
|
|
5433
|
+
success: false,
|
|
5434
|
+
message: "SAGE project server is unavailable in this runtime"
|
|
5435
|
+
};
|
|
5436
|
+
}
|
|
5437
|
+
const connection = new SageProjectServerConnection(projectRoot);
|
|
5438
|
+
try {
|
|
5439
|
+
const result = await connection.shutdown(`WebUI request: ${action}`);
|
|
5440
|
+
return {
|
|
5441
|
+
serviceId: "sage",
|
|
5442
|
+
action,
|
|
5443
|
+
success: result.stopped,
|
|
5444
|
+
message: result.stopped ? `SAGE memory server ${action} requested` : `SAGE memory server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5445
|
+
};
|
|
5446
|
+
} catch (error2) {
|
|
5447
|
+
return {
|
|
5448
|
+
serviceId: "sage",
|
|
5449
|
+
action,
|
|
5450
|
+
success: false,
|
|
5451
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5452
|
+
};
|
|
5453
|
+
} finally {
|
|
5454
|
+
connection.close();
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
async function killChronicleServer(projectRoot, action) {
|
|
5458
|
+
const options = resolveChronicleProjectServerOptions({ projectRoot });
|
|
5459
|
+
const client = new ChronicleProjectServerClient(options);
|
|
5460
|
+
try {
|
|
5461
|
+
const result = await client.shutdown(`WebUI request: ${action}`);
|
|
5462
|
+
return {
|
|
5463
|
+
serviceId: "chronicle",
|
|
5464
|
+
action,
|
|
5465
|
+
success: result.stopped,
|
|
5466
|
+
message: result.stopped ? `Chronicle telemetry server ${action} requested` : `Chronicle telemetry server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5467
|
+
};
|
|
5468
|
+
} catch (error2) {
|
|
5469
|
+
return {
|
|
5470
|
+
serviceId: "chronicle",
|
|
5471
|
+
action,
|
|
5472
|
+
success: false,
|
|
5473
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5474
|
+
};
|
|
5475
|
+
} finally {
|
|
5476
|
+
client.close();
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
async function killCodebaseIndexServer(projectRoot, indexDir, action) {
|
|
5480
|
+
try {
|
|
5481
|
+
const result = await shutdownCodebaseIndexServer(
|
|
5482
|
+
projectRoot,
|
|
5483
|
+
indexDir,
|
|
5484
|
+
`websocket-request:${action}`
|
|
5485
|
+
);
|
|
5486
|
+
return {
|
|
5487
|
+
serviceId: "codebase-index",
|
|
5488
|
+
action,
|
|
5489
|
+
success: result.stopped,
|
|
5490
|
+
message: result.stopped ? `Codebase index server ${action} requested` : `Codebase index server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5491
|
+
};
|
|
5492
|
+
} catch (error2) {
|
|
5493
|
+
return {
|
|
5494
|
+
serviceId: "codebase-index",
|
|
5495
|
+
action,
|
|
5496
|
+
success: false,
|
|
5497
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5498
|
+
};
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
async function killMailboxServer(projectRoot, action) {
|
|
5502
|
+
if (!isMailboxProjectServerAvailable()) {
|
|
5503
|
+
return {
|
|
5504
|
+
serviceId: "mailbox",
|
|
5505
|
+
action,
|
|
5506
|
+
success: false,
|
|
5507
|
+
message: "Mailbox project server is unavailable in this runtime"
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
5510
|
+
const connection = new MailboxProjectServerConnection(
|
|
5511
|
+
resolveWstackPaths2({ projectRoot }).projectDir
|
|
5512
|
+
);
|
|
5513
|
+
try {
|
|
5514
|
+
const result = await connection.shutdown(`WebUI request: ${action}`);
|
|
5515
|
+
return {
|
|
5516
|
+
serviceId: "mailbox",
|
|
5517
|
+
action,
|
|
5518
|
+
success: result.stopped,
|
|
5519
|
+
message: result.stopped ? `Mailbox IPC server ${action} requested` : `Mailbox IPC server ${action} failed: ${result.reason ?? "unknown"}`
|
|
5520
|
+
};
|
|
5521
|
+
} catch (error2) {
|
|
5522
|
+
return {
|
|
5523
|
+
serviceId: "mailbox",
|
|
5524
|
+
action,
|
|
5525
|
+
success: false,
|
|
5526
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
5527
|
+
};
|
|
5528
|
+
} finally {
|
|
5529
|
+
connection.close();
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5272
5532
|
function failureService(id, label, required, mode, error2, latencyMs) {
|
|
5273
5533
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
5274
5534
|
return {
|
|
@@ -5429,9 +5689,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5429
5689
|
const cwd = projectRoot || void 0;
|
|
5430
5690
|
try {
|
|
5431
5691
|
const { execFile: ef } = await import("node:child_process");
|
|
5432
|
-
const git = (args) => new Promise((
|
|
5692
|
+
const git = (args) => new Promise((resolve16) => {
|
|
5433
5693
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
5434
|
-
|
|
5694
|
+
resolve16(err ? "" : stdout.trim());
|
|
5435
5695
|
});
|
|
5436
5696
|
});
|
|
5437
5697
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -5457,12 +5717,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
5457
5717
|
function makeGit(cwd) {
|
|
5458
5718
|
return async (args) => {
|
|
5459
5719
|
const { execFile: ef } = await import("node:child_process");
|
|
5460
|
-
return new Promise((
|
|
5720
|
+
return new Promise((resolve16) => {
|
|
5461
5721
|
ef(
|
|
5462
5722
|
"git",
|
|
5463
5723
|
args,
|
|
5464
5724
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
5465
|
-
(err, stdout) =>
|
|
5725
|
+
(err, stdout) => resolve16(err ? "" : stdout)
|
|
5466
5726
|
);
|
|
5467
5727
|
});
|
|
5468
5728
|
};
|
|
@@ -5486,15 +5746,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5486
5746
|
if (!m) continue;
|
|
5487
5747
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5488
5748
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5489
|
-
let
|
|
5490
|
-
if (
|
|
5749
|
+
let path34 = m[3] ?? "";
|
|
5750
|
+
if (path34 === "") {
|
|
5491
5751
|
i += 1;
|
|
5492
|
-
|
|
5752
|
+
path34 = parts[i + 1] ?? parts[i] ?? "";
|
|
5493
5753
|
i += 1;
|
|
5494
5754
|
}
|
|
5495
|
-
if (!
|
|
5496
|
-
const prev = counts.get(
|
|
5497
|
-
counts.set(
|
|
5755
|
+
if (!path34) continue;
|
|
5756
|
+
const prev = counts.get(path34) ?? { added: 0, deleted: 0 };
|
|
5757
|
+
counts.set(path34, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5498
5758
|
}
|
|
5499
5759
|
};
|
|
5500
5760
|
parseNumstat(unstagedNumstat);
|
|
@@ -5506,7 +5766,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5506
5766
|
if (!rec || rec.length < 3) continue;
|
|
5507
5767
|
const x = rec[0] ?? " ";
|
|
5508
5768
|
const y = rec[1] ?? " ";
|
|
5509
|
-
const
|
|
5769
|
+
const path34 = rec.slice(3);
|
|
5510
5770
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5511
5771
|
if (isRename) i += 1;
|
|
5512
5772
|
let status;
|
|
@@ -5518,13 +5778,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5518
5778
|
else if (x === "D" || y === "D") status = "D";
|
|
5519
5779
|
else status = "M";
|
|
5520
5780
|
const staged = x !== " " && x !== "?";
|
|
5521
|
-
let added = counts.get(
|
|
5522
|
-
let deleted = counts.get(
|
|
5781
|
+
let added = counts.get(path34)?.added ?? 0;
|
|
5782
|
+
let deleted = counts.get(path34)?.deleted ?? 0;
|
|
5523
5783
|
if (status === "?") {
|
|
5524
5784
|
added = 0;
|
|
5525
5785
|
deleted = 0;
|
|
5526
5786
|
}
|
|
5527
|
-
files.push({ path:
|
|
5787
|
+
files.push({ path: path34, status, added, deleted, staged });
|
|
5528
5788
|
}
|
|
5529
5789
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5530
5790
|
} catch (err) {
|
|
@@ -5535,10 +5795,10 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5535
5795
|
}
|
|
5536
5796
|
}
|
|
5537
5797
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5538
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5798
|
+
async function handleGitDiff(ws, projectRoot, path34) {
|
|
5539
5799
|
const cwd = projectRoot || void 0;
|
|
5540
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5541
|
-
if (!
|
|
5800
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path34, ...extra } });
|
|
5801
|
+
if (!path34 || path34.includes("\0") || path34.includes("..") || nodePath.isAbsolute(path34)) {
|
|
5542
5802
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5543
5803
|
return;
|
|
5544
5804
|
}
|
|
@@ -5546,10 +5806,10 @@ async function handleGitDiff(ws, projectRoot, path33) {
|
|
|
5546
5806
|
const git = makeGit(cwd);
|
|
5547
5807
|
const { readFile: readFile13 } = await import("node:fs/promises");
|
|
5548
5808
|
const { join: join18 } = await import("node:path");
|
|
5549
|
-
const oldText = await git(["show", `HEAD:${
|
|
5809
|
+
const oldText = await git(["show", `HEAD:${path34}`]);
|
|
5550
5810
|
let newText = "";
|
|
5551
5811
|
try {
|
|
5552
|
-
const abs = cwd ? join18(cwd,
|
|
5812
|
+
const abs = cwd ? join18(cwd, path34) : path34;
|
|
5553
5813
|
const buf = await readFile13(abs);
|
|
5554
5814
|
if (buf.includes(0)) {
|
|
5555
5815
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5629,7 +5889,7 @@ import { execFile } from "node:child_process";
|
|
|
5629
5889
|
var GIT_TIMEOUT_MS = 1e4;
|
|
5630
5890
|
var GIT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
5631
5891
|
function gitStdout(cwd, args) {
|
|
5632
|
-
return new Promise((
|
|
5892
|
+
return new Promise((resolve16) => {
|
|
5633
5893
|
execFile(
|
|
5634
5894
|
"git",
|
|
5635
5895
|
[...args],
|
|
@@ -5640,7 +5900,7 @@ function gitStdout(cwd, args) {
|
|
|
5640
5900
|
timeout: GIT_TIMEOUT_MS,
|
|
5641
5901
|
maxBuffer: GIT_MAX_OUTPUT_BYTES
|
|
5642
5902
|
},
|
|
5643
|
-
(error2, stdout) =>
|
|
5903
|
+
(error2, stdout) => resolve16(error2 ? null : stdout)
|
|
5644
5904
|
);
|
|
5645
5905
|
});
|
|
5646
5906
|
}
|
|
@@ -5906,13 +6166,13 @@ var GoalWebSocketHandler = class {
|
|
|
5906
6166
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
5907
6167
|
try {
|
|
5908
6168
|
const { exec } = await import("node:child_process");
|
|
5909
|
-
const result = await new Promise((
|
|
6169
|
+
const result = await new Promise((resolve16) => {
|
|
5910
6170
|
exec("npx tsc --noEmit", { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
5911
6171
|
if (err && err.code === "ENOENT") {
|
|
5912
|
-
|
|
6172
|
+
resolve16("[verify] tsc not found \u2014 skipping");
|
|
5913
6173
|
return;
|
|
5914
6174
|
}
|
|
5915
|
-
|
|
6175
|
+
resolve16(stdout + stderr);
|
|
5916
6176
|
});
|
|
5917
6177
|
});
|
|
5918
6178
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
@@ -6591,7 +6851,7 @@ function pushEvent(event) {
|
|
|
6591
6851
|
}
|
|
6592
6852
|
}
|
|
6593
6853
|
function parseBody(req) {
|
|
6594
|
-
return new Promise((
|
|
6854
|
+
return new Promise((resolve16, reject) => {
|
|
6595
6855
|
let body = "";
|
|
6596
6856
|
let bodyBytes = 0;
|
|
6597
6857
|
let tooLarge = false;
|
|
@@ -6611,7 +6871,7 @@ function parseBody(req) {
|
|
|
6611
6871
|
return;
|
|
6612
6872
|
}
|
|
6613
6873
|
try {
|
|
6614
|
-
|
|
6874
|
+
resolve16(JSON.parse(body));
|
|
6615
6875
|
} catch {
|
|
6616
6876
|
reject(new Error("Invalid JSON"));
|
|
6617
6877
|
}
|
|
@@ -6696,7 +6956,7 @@ function getAnalyticsBuffer() {
|
|
|
6696
6956
|
// src/server/http-server.ts
|
|
6697
6957
|
import * as fs9 from "node:fs/promises";
|
|
6698
6958
|
import * as http from "node:http";
|
|
6699
|
-
import * as
|
|
6959
|
+
import * as path12 from "node:path";
|
|
6700
6960
|
import * as v8 from "node:v8";
|
|
6701
6961
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6702
6962
|
|
|
@@ -6776,6 +7036,156 @@ async function handleCodemapSymbols(res, deps2, file) {
|
|
|
6776
7036
|
);
|
|
6777
7037
|
}
|
|
6778
7038
|
|
|
7039
|
+
// src/server/deadcode-handlers.ts
|
|
7040
|
+
import * as path10 from "node:path";
|
|
7041
|
+
import { runDeadCodeScan } from "@wrongstack/tools/codebase-index";
|
|
7042
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
7043
|
+
function readJsonBody(req) {
|
|
7044
|
+
return new Promise((resolve16, reject) => {
|
|
7045
|
+
const chunks = [];
|
|
7046
|
+
let total = 0;
|
|
7047
|
+
req.on("data", (chunk) => {
|
|
7048
|
+
total += chunk.length;
|
|
7049
|
+
if (total > MAX_BODY_BYTES) {
|
|
7050
|
+
req.destroy(new Error("Request body too large"));
|
|
7051
|
+
reject(new Error("Request body exceeds 10 MiB limit"));
|
|
7052
|
+
return;
|
|
7053
|
+
}
|
|
7054
|
+
chunks.push(chunk);
|
|
7055
|
+
});
|
|
7056
|
+
req.on("end", () => resolve16(Buffer.concat(chunks).toString("utf8")));
|
|
7057
|
+
req.on("error", (err) => reject(err));
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
async function handleDeadCodeScan(res, deps2, req) {
|
|
7061
|
+
try {
|
|
7062
|
+
let body = {};
|
|
7063
|
+
const raw = await readJsonBody(req);
|
|
7064
|
+
if (raw) {
|
|
7065
|
+
try {
|
|
7066
|
+
body = JSON.parse(raw);
|
|
7067
|
+
} catch {
|
|
7068
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7069
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
const scanIndexDir = body.indexDir ?? deps2.indexDir;
|
|
7074
|
+
if (scanIndexDir) {
|
|
7075
|
+
const resolvedRoot = path10.resolve(deps2.projectRoot);
|
|
7076
|
+
const resolvedIndex = path10.resolve(deps2.projectRoot, scanIndexDir);
|
|
7077
|
+
if (resolvedIndex !== resolvedRoot && !resolvedIndex.startsWith(resolvedRoot + path10.sep)) {
|
|
7078
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7079
|
+
res.end(JSON.stringify({ error: "Invalid indexDir: must be within project root" }));
|
|
7080
|
+
return;
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
const result = runDeadCodeScan(deps2.projectRoot, {
|
|
7084
|
+
indexDir: scanIndexDir,
|
|
7085
|
+
userEntryPoints: body.entryPoints
|
|
7086
|
+
});
|
|
7087
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7088
|
+
res.end(JSON.stringify(result));
|
|
7089
|
+
} catch (err) {
|
|
7090
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
7091
|
+
res.end(
|
|
7092
|
+
JSON.stringify({
|
|
7093
|
+
error: "Dead-code scan failed",
|
|
7094
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
7095
|
+
})
|
|
7096
|
+
);
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
function handleDeadCodeActionPlan(res, _deps, req) {
|
|
7100
|
+
return readJsonBody(req).then((raw) => {
|
|
7101
|
+
let parsed;
|
|
7102
|
+
try {
|
|
7103
|
+
parsed = JSON.parse(raw);
|
|
7104
|
+
} catch {
|
|
7105
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7106
|
+
res.end(JSON.stringify({ error: "Invalid scan result JSON" }));
|
|
7107
|
+
return;
|
|
7108
|
+
}
|
|
7109
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.deadPackages) || !Array.isArray(parsed.deadFiles) || !Array.isArray(parsed.deadSymbols)) {
|
|
7110
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7111
|
+
res.end(
|
|
7112
|
+
JSON.stringify({
|
|
7113
|
+
error: "Invalid scan result: missing or malformed required fields (deadPackages, deadFiles, deadSymbols)"
|
|
7114
|
+
})
|
|
7115
|
+
);
|
|
7116
|
+
return;
|
|
7117
|
+
}
|
|
7118
|
+
const result = parsed;
|
|
7119
|
+
const plan = buildActionPlan(result);
|
|
7120
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7121
|
+
res.end(JSON.stringify(plan));
|
|
7122
|
+
}).catch((err) => {
|
|
7123
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
7124
|
+
res.end(
|
|
7125
|
+
JSON.stringify({
|
|
7126
|
+
error: "Failed to read request body",
|
|
7127
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
7128
|
+
})
|
|
7129
|
+
);
|
|
7130
|
+
});
|
|
7131
|
+
}
|
|
7132
|
+
function buildActionPlan(result) {
|
|
7133
|
+
const files = /* @__PURE__ */ new Map();
|
|
7134
|
+
for (const dp of result.deadPackages) {
|
|
7135
|
+
const pseudoFile = {
|
|
7136
|
+
file: `${dp.package}/ (package)`,
|
|
7137
|
+
symbolCount: dp.fileCount,
|
|
7138
|
+
symbols: [`remove package ${dp.package} (${dp.fileCount} files, path: ${dp.path})`],
|
|
7139
|
+
priority: 0
|
|
7140
|
+
};
|
|
7141
|
+
files.set(pseudoFile.file, pseudoFile);
|
|
7142
|
+
}
|
|
7143
|
+
for (const df of result.deadFiles) {
|
|
7144
|
+
const existing = files.get(df.file);
|
|
7145
|
+
if (existing) {
|
|
7146
|
+
if (existing.priority > 1) existing.priority = 1;
|
|
7147
|
+
existing.symbolCount += df.symbolCount;
|
|
7148
|
+
continue;
|
|
7149
|
+
}
|
|
7150
|
+
files.set(df.file, {
|
|
7151
|
+
file: df.file,
|
|
7152
|
+
symbolCount: df.symbolCount,
|
|
7153
|
+
symbols: [`entire file (${df.symbolCount} symbols) is dead`],
|
|
7154
|
+
priority: 1
|
|
7155
|
+
});
|
|
7156
|
+
}
|
|
7157
|
+
const deadInAliveFiles = /* @__PURE__ */ new Map();
|
|
7158
|
+
const deadFileSet = new Set(result.deadFiles.map((df) => df.file));
|
|
7159
|
+
for (const ds of result.deadSymbols) {
|
|
7160
|
+
if (deadFileSet.has(ds.file)) continue;
|
|
7161
|
+
const list = deadInAliveFiles.get(ds.file) ?? [];
|
|
7162
|
+
list.push(`${ds.kind} ${ds.name} (line ${ds.line})`);
|
|
7163
|
+
deadInAliveFiles.set(ds.file, list);
|
|
7164
|
+
}
|
|
7165
|
+
for (const [file, symbols] of deadInAliveFiles) {
|
|
7166
|
+
const existing = files.get(file);
|
|
7167
|
+
if (existing) {
|
|
7168
|
+
existing.symbols.push(...symbols);
|
|
7169
|
+
existing.symbolCount += symbols.length;
|
|
7170
|
+
continue;
|
|
7171
|
+
}
|
|
7172
|
+
files.set(file, {
|
|
7173
|
+
file,
|
|
7174
|
+
symbolCount: symbols.length,
|
|
7175
|
+
symbols,
|
|
7176
|
+
priority: 2
|
|
7177
|
+
});
|
|
7178
|
+
}
|
|
7179
|
+
const sorted = [...files.values()].sort((a, b) => a.priority - b.priority || a.file.localeCompare(b.file));
|
|
7180
|
+
return {
|
|
7181
|
+
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.`,
|
|
7182
|
+
files: sorted,
|
|
7183
|
+
totalDeadSymbols: result.stats.dead,
|
|
7184
|
+
totalDeadFiles: result.deadFiles.length,
|
|
7185
|
+
totalDeadPackages: result.deadPackages.length
|
|
7186
|
+
};
|
|
7187
|
+
}
|
|
7188
|
+
|
|
6779
7189
|
// src/server/http-server/api-handlers.ts
|
|
6780
7190
|
async function handleApiSessions(res, globalRoot) {
|
|
6781
7191
|
if (!globalRoot) {
|
|
@@ -6997,8 +7407,8 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
6997
7407
|
res.end(JSON.stringify({ error: String(err) }));
|
|
6998
7408
|
}
|
|
6999
7409
|
}
|
|
7000
|
-
function
|
|
7001
|
-
return new Promise((
|
|
7410
|
+
function readJsonBody2(req) {
|
|
7411
|
+
return new Promise((resolve16, reject) => {
|
|
7002
7412
|
let data = "";
|
|
7003
7413
|
req.on("data", (chunk) => {
|
|
7004
7414
|
data += chunk;
|
|
@@ -7009,7 +7419,7 @@ function readJsonBody(req) {
|
|
|
7009
7419
|
});
|
|
7010
7420
|
req.on("end", () => {
|
|
7011
7421
|
try {
|
|
7012
|
-
|
|
7422
|
+
resolve16(data ? JSON.parse(data) : {});
|
|
7013
7423
|
} catch (err) {
|
|
7014
7424
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
7015
7425
|
}
|
|
@@ -7025,7 +7435,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
7025
7435
|
}
|
|
7026
7436
|
let body;
|
|
7027
7437
|
try {
|
|
7028
|
-
body = await
|
|
7438
|
+
body = await readJsonBody2(req);
|
|
7029
7439
|
} catch {
|
|
7030
7440
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7031
7441
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7127,7 +7537,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
7127
7537
|
}
|
|
7128
7538
|
let body = {};
|
|
7129
7539
|
try {
|
|
7130
|
-
body = await
|
|
7540
|
+
body = await readJsonBody2(req);
|
|
7131
7541
|
} catch {
|
|
7132
7542
|
}
|
|
7133
7543
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
@@ -7168,7 +7578,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7168
7578
|
}
|
|
7169
7579
|
let body;
|
|
7170
7580
|
try {
|
|
7171
|
-
body = await
|
|
7581
|
+
body = await readJsonBody2(req);
|
|
7172
7582
|
} catch {
|
|
7173
7583
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7174
7584
|
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
@@ -7232,12 +7642,12 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7232
7642
|
|
|
7233
7643
|
// src/server/projects-manifest.ts
|
|
7234
7644
|
import * as fs8 from "node:fs/promises";
|
|
7235
|
-
import * as
|
|
7645
|
+
import * as path11 from "node:path";
|
|
7236
7646
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7237
7647
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7238
7648
|
function projectsJsonPath(globalConfigPath) {
|
|
7239
|
-
const base =
|
|
7240
|
-
return
|
|
7649
|
+
const base = path11.dirname(globalConfigPath);
|
|
7650
|
+
return path11.join(base, "projects.json");
|
|
7241
7651
|
}
|
|
7242
7652
|
async function loadManifest(globalConfigPath) {
|
|
7243
7653
|
try {
|
|
@@ -7250,37 +7660,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7250
7660
|
}
|
|
7251
7661
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7252
7662
|
const file = projectsJsonPath(globalConfigPath);
|
|
7253
|
-
await fs8.mkdir(
|
|
7663
|
+
await fs8.mkdir(path11.dirname(file), { recursive: true });
|
|
7254
7664
|
await fs8.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7255
7665
|
}
|
|
7256
7666
|
function generateProjectSlug(rootPath) {
|
|
7257
7667
|
return projectSlug(rootPath);
|
|
7258
7668
|
}
|
|
7259
7669
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7260
|
-
const base =
|
|
7261
|
-
const dir =
|
|
7670
|
+
const base = path11.dirname(globalConfigPath);
|
|
7671
|
+
const dir = path11.join(base, "projects", slug);
|
|
7262
7672
|
await fs8.mkdir(dir, { recursive: true });
|
|
7263
7673
|
return dir;
|
|
7264
7674
|
}
|
|
7265
7675
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7266
|
-
const root =
|
|
7676
|
+
const root = path11.resolve(options.projectRoot);
|
|
7267
7677
|
const file = projectsJsonPath(globalConfigPath);
|
|
7268
7678
|
let entry;
|
|
7269
7679
|
await withFileLock(file, async () => {
|
|
7270
7680
|
const manifest = await loadManifest(globalConfigPath);
|
|
7271
7681
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7272
|
-
entry = manifest.projects.find((candidate) =>
|
|
7682
|
+
entry = manifest.projects.find((candidate) => path11.resolve(candidate.root) === root);
|
|
7273
7683
|
if (entry) {
|
|
7274
7684
|
entry.lastSeen = now;
|
|
7275
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7685
|
+
if (options.workingDir) entry.lastWorkingDir = path11.resolve(options.workingDir);
|
|
7276
7686
|
} else {
|
|
7277
7687
|
entry = {
|
|
7278
|
-
name: options.name ??
|
|
7688
|
+
name: options.name ?? path11.basename(root),
|
|
7279
7689
|
root,
|
|
7280
7690
|
slug: generateProjectSlug(root),
|
|
7281
7691
|
createdAt: now,
|
|
7282
7692
|
lastSeen: now,
|
|
7283
|
-
lastWorkingDir: options.workingDir ?
|
|
7693
|
+
lastWorkingDir: options.workingDir ? path11.resolve(options.workingDir) : void 0
|
|
7284
7694
|
};
|
|
7285
7695
|
manifest.projects.push(entry);
|
|
7286
7696
|
}
|
|
@@ -7691,9 +8101,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
7691
8101
|
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
8102
|
}
|
|
7693
8103
|
function isInsideDist(candidate, distDir) {
|
|
7694
|
-
const root =
|
|
7695
|
-
const resolved =
|
|
7696
|
-
return resolved === root || resolved.startsWith(root +
|
|
8104
|
+
const root = path12.resolve(distDir);
|
|
8105
|
+
const resolved = path12.resolve(candidate);
|
|
8106
|
+
return resolved === root || resolved.startsWith(root + path12.sep);
|
|
7697
8107
|
}
|
|
7698
8108
|
function decodeSessionId(segment) {
|
|
7699
8109
|
try {
|
|
@@ -7713,7 +8123,7 @@ function strictDecodeParam(segment, res) {
|
|
|
7713
8123
|
}
|
|
7714
8124
|
function createHttpServer(opts) {
|
|
7715
8125
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
7716
|
-
const distDir =
|
|
8126
|
+
const distDir = path12.resolve(opts.distDir);
|
|
7717
8127
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
7718
8128
|
let techStackRuntime = null;
|
|
7719
8129
|
const getTechStackRuntime = async () => {
|
|
@@ -7932,6 +8342,42 @@ function createHttpServer(opts) {
|
|
|
7932
8342
|
);
|
|
7933
8343
|
return;
|
|
7934
8344
|
}
|
|
8345
|
+
if (url.pathname === "/api/deadcode/scan" && req.method === "POST") {
|
|
8346
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8347
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8348
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8349
|
+
return;
|
|
8350
|
+
}
|
|
8351
|
+
if (!opts.projectRoot) {
|
|
8352
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8353
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8354
|
+
return;
|
|
8355
|
+
}
|
|
8356
|
+
const deadCodeDeps = {
|
|
8357
|
+
projectRoot: opts.projectRoot,
|
|
8358
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8359
|
+
};
|
|
8360
|
+
await handleDeadCodeScan(res, deadCodeDeps, req);
|
|
8361
|
+
return;
|
|
8362
|
+
}
|
|
8363
|
+
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
8364
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
8365
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
8366
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8367
|
+
return;
|
|
8368
|
+
}
|
|
8369
|
+
if (!opts.projectRoot) {
|
|
8370
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
8371
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
8372
|
+
return;
|
|
8373
|
+
}
|
|
8374
|
+
const deadCodeDeps = {
|
|
8375
|
+
projectRoot: opts.projectRoot,
|
|
8376
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
8377
|
+
};
|
|
8378
|
+
await handleDeadCodeActionPlan(res, deadCodeDeps, req);
|
|
8379
|
+
return;
|
|
8380
|
+
}
|
|
7935
8381
|
if (url.pathname.startsWith("/api/techstack/")) {
|
|
7936
8382
|
if (requireAccessToken && !accessTokenOk) {
|
|
7937
8383
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -8064,17 +8510,17 @@ function createHttpServer(opts) {
|
|
|
8064
8510
|
}
|
|
8065
8511
|
let filePath;
|
|
8066
8512
|
if (url.pathname === "/" || url.pathname === "") {
|
|
8067
|
-
filePath =
|
|
8513
|
+
filePath = path12.join(distDir, "index.html");
|
|
8068
8514
|
} else {
|
|
8069
|
-
filePath =
|
|
8515
|
+
filePath = path12.join(distDir, url.pathname);
|
|
8070
8516
|
}
|
|
8071
|
-
const resolvedPath =
|
|
8517
|
+
const resolvedPath = path12.resolve(filePath);
|
|
8072
8518
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
8073
8519
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
8074
8520
|
res.end("Forbidden");
|
|
8075
8521
|
return;
|
|
8076
8522
|
}
|
|
8077
|
-
const ext =
|
|
8523
|
+
const ext = path12.extname(resolvedPath);
|
|
8078
8524
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
8079
8525
|
res.setHeader("Content-Type", contentType);
|
|
8080
8526
|
setStaticSecurityHeaders(res);
|
|
@@ -8098,7 +8544,7 @@ function createHttpServer(opts) {
|
|
|
8098
8544
|
} catch (err) {
|
|
8099
8545
|
if (err.code === "ENOENT") {
|
|
8100
8546
|
try {
|
|
8101
|
-
const html = await fs9.readFile(
|
|
8547
|
+
const html = await fs9.readFile(path12.join(distDir, "index.html"), "utf8");
|
|
8102
8548
|
setStaticSecurityHeaders(res);
|
|
8103
8549
|
res.writeHead(200, {
|
|
8104
8550
|
"Content-Type": "text/html",
|
|
@@ -8128,14 +8574,14 @@ function createHttpServer(opts) {
|
|
|
8128
8574
|
|
|
8129
8575
|
// src/server/instance-registry.ts
|
|
8130
8576
|
import * as os from "node:os";
|
|
8131
|
-
import * as
|
|
8577
|
+
import * as path13 from "node:path";
|
|
8132
8578
|
import * as fs10 from "node:fs/promises";
|
|
8133
8579
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8134
8580
|
function defaultBaseDir() {
|
|
8135
|
-
return
|
|
8581
|
+
return path13.join(os.homedir(), ".wrongstack");
|
|
8136
8582
|
}
|
|
8137
8583
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8138
|
-
return
|
|
8584
|
+
return path13.join(baseDir, "webui-instances.json");
|
|
8139
8585
|
}
|
|
8140
8586
|
function isPidAlive(pid) {
|
|
8141
8587
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -11610,16 +12056,16 @@ function getSurfaceDefaultPorts(surface) {
|
|
|
11610
12056
|
return { http: SURFACE_DEFAULT_PORTS[surface].http };
|
|
11611
12057
|
}
|
|
11612
12058
|
function isPortFree(host, port) {
|
|
11613
|
-
return new Promise((
|
|
12059
|
+
return new Promise((resolve16) => {
|
|
11614
12060
|
const srv = net.createServer();
|
|
11615
|
-
srv.once("error", () =>
|
|
12061
|
+
srv.once("error", () => resolve16(false));
|
|
11616
12062
|
srv.once("listening", () => {
|
|
11617
|
-
srv.close(() =>
|
|
12063
|
+
srv.close(() => resolve16(true));
|
|
11618
12064
|
});
|
|
11619
12065
|
try {
|
|
11620
12066
|
srv.listen(port, host);
|
|
11621
12067
|
} catch {
|
|
11622
|
-
|
|
12068
|
+
resolve16(false);
|
|
11623
12069
|
}
|
|
11624
12070
|
});
|
|
11625
12071
|
}
|
|
@@ -11644,10 +12090,10 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
11644
12090
|
import { spawn as spawn2 } from "node:child_process";
|
|
11645
12091
|
import { existsSync } from "node:fs";
|
|
11646
12092
|
import { findPackageJSON } from "node:module";
|
|
11647
|
-
import * as
|
|
12093
|
+
import * as path14 from "node:path";
|
|
11648
12094
|
function resolveDistDir(input) {
|
|
11649
12095
|
const options = typeof input === "string" ? { explicitDistDir: input } : input ?? {};
|
|
11650
|
-
if (options.explicitDistDir) return
|
|
12096
|
+
if (options.explicitDistDir) return path14.resolve(options.explicitDistDir);
|
|
11651
12097
|
const exists = options.exists ?? existsSync;
|
|
11652
12098
|
let packageTarget;
|
|
11653
12099
|
try {
|
|
@@ -11659,15 +12105,15 @@ function resolveDistDir(input) {
|
|
|
11659
12105
|
);
|
|
11660
12106
|
}
|
|
11661
12107
|
if (!packageTarget) return null;
|
|
11662
|
-
const distDir =
|
|
12108
|
+
const distDir = path14.basename(packageTarget) === "package.json" ? path14.join(path14.dirname(packageTarget), "dist") : path14.dirname(packageTarget);
|
|
11663
12109
|
if (options.exists === void 0 && options.resolvePackageJson) return distDir;
|
|
11664
|
-
return exists(
|
|
12110
|
+
return exists(path14.join(distDir, "index.html")) ? distDir : null;
|
|
11665
12111
|
}
|
|
11666
12112
|
async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
11667
12113
|
const exists = deps2.exists ?? existsSync;
|
|
11668
12114
|
if (explicitDistDir) {
|
|
11669
|
-
const resolved2 =
|
|
11670
|
-
return exists(
|
|
12115
|
+
const resolved2 = path14.resolve(explicitDistDir);
|
|
12116
|
+
return exists(path14.join(resolved2, "index.html")) ? resolved2 : null;
|
|
11671
12117
|
}
|
|
11672
12118
|
const resolveOptions = {
|
|
11673
12119
|
resolvePackageJson: deps2.resolvePackageJson,
|
|
@@ -11679,7 +12125,7 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
11679
12125
|
try {
|
|
11680
12126
|
const packageJson = deps2.resolvePackageJson ? deps2.resolvePackageJson("@wrongstack/webui/package.json") : findPackageJSON("@wrongstack/webui", import.meta.url);
|
|
11681
12127
|
if (!packageJson) throw new Error("not found");
|
|
11682
|
-
packageDir =
|
|
12128
|
+
packageDir = path14.dirname(packageJson);
|
|
11683
12129
|
} catch {
|
|
11684
12130
|
throw new Error(
|
|
11685
12131
|
"@wrongstack/webui package could not be resolved. Install workspace dependencies and rebuild the CLI."
|
|
@@ -11688,8 +12134,8 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
11688
12134
|
const findRoot = deps2.findWorkspaceRoot ?? ((pkgDir) => {
|
|
11689
12135
|
let dir = pkgDir;
|
|
11690
12136
|
for (let i = 0; i < 10; i++) {
|
|
11691
|
-
if (existsSync(
|
|
11692
|
-
const parent =
|
|
12137
|
+
if (existsSync(path14.join(dir, "pnpm-workspace.yaml"))) return dir;
|
|
12138
|
+
const parent = path14.dirname(dir);
|
|
11693
12139
|
if (parent === dir) return null;
|
|
11694
12140
|
dir = parent;
|
|
11695
12141
|
}
|
|
@@ -11748,7 +12194,7 @@ async function startStaticServe(opts, deps2 = {}) {
|
|
|
11748
12194
|
return { server, port: opts.httpPort };
|
|
11749
12195
|
}
|
|
11750
12196
|
function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
11751
|
-
return new Promise((
|
|
12197
|
+
return new Promise((resolve16, reject) => {
|
|
11752
12198
|
const child = spawn2("pnpm", ["--filter", workspace, "build"], {
|
|
11753
12199
|
cwd,
|
|
11754
12200
|
shell: process.platform === "win32",
|
|
@@ -11766,14 +12212,14 @@ function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
|
11766
12212
|
});
|
|
11767
12213
|
child.once("close", (code) => {
|
|
11768
12214
|
clearTimeout(timer);
|
|
11769
|
-
if (code === 0)
|
|
12215
|
+
if (code === 0) resolve16();
|
|
11770
12216
|
else reject(new Error(`pnpm build exited with code ${String(code)}`));
|
|
11771
12217
|
});
|
|
11772
12218
|
});
|
|
11773
12219
|
}
|
|
11774
12220
|
|
|
11775
12221
|
// src/server/embedded-lifecycle.ts
|
|
11776
|
-
import * as
|
|
12222
|
+
import * as path15 from "node:path";
|
|
11777
12223
|
|
|
11778
12224
|
// src/server/network-info.ts
|
|
11779
12225
|
import * as os2 from "node:os";
|
|
@@ -11839,7 +12285,7 @@ function registerWebuiInstance(p, deps2 = {}) {
|
|
|
11839
12285
|
httpPort: p.httpPort,
|
|
11840
12286
|
host: p.host,
|
|
11841
12287
|
projectRoot: p.projectRoot,
|
|
11842
|
-
projectName:
|
|
12288
|
+
projectName: path15.basename(p.projectRoot) || p.projectRoot,
|
|
11843
12289
|
startedAt: p.startedAt,
|
|
11844
12290
|
url: buildWebUIAccessUrl({
|
|
11845
12291
|
host: p.host,
|
|
@@ -12059,7 +12505,7 @@ ${text2}` : text2;
|
|
|
12059
12505
|
|
|
12060
12506
|
// src/server/client-presence.ts
|
|
12061
12507
|
import * as crypto2 from "node:crypto";
|
|
12062
|
-
import * as
|
|
12508
|
+
import * as path16 from "node:path";
|
|
12063
12509
|
import {
|
|
12064
12510
|
getSharedProjectMailbox as getSharedProjectMailbox2,
|
|
12065
12511
|
resolveProjectDir as resolveProjectDir2
|
|
@@ -12077,7 +12523,7 @@ function createWebuiClientPresence(deps2) {
|
|
|
12077
12523
|
if (!deps2.projectRoot) return null;
|
|
12078
12524
|
try {
|
|
12079
12525
|
const projectRoot = deps2.projectRoot;
|
|
12080
|
-
const projectName =
|
|
12526
|
+
const projectName = path16.basename(projectRoot);
|
|
12081
12527
|
const nextMailbox = getSharedProjectMailbox2(
|
|
12082
12528
|
resolveProjectDir2(projectRoot, wstackGlobalRoot()),
|
|
12083
12529
|
deps2.events,
|
|
@@ -13035,7 +13481,7 @@ function seedContextMeta(config, context) {
|
|
|
13035
13481
|
meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
|
|
13036
13482
|
meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
|
|
13037
13483
|
meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
|
|
13038
|
-
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] :
|
|
13484
|
+
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 15e3;
|
|
13039
13485
|
meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
|
|
13040
13486
|
meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
|
|
13041
13487
|
const cascade = autoReviewExt?.["cascadeOn"];
|
|
@@ -13055,7 +13501,7 @@ function seedContextMeta(config, context) {
|
|
|
13055
13501
|
|
|
13056
13502
|
// src/server/pref-helpers.ts
|
|
13057
13503
|
import * as fs12 from "node:fs/promises";
|
|
13058
|
-
import * as
|
|
13504
|
+
import * as path17 from "node:path";
|
|
13059
13505
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
13060
13506
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
13061
13507
|
var PREF_KEYS = [
|
|
@@ -13151,7 +13597,7 @@ function prefSnapshot(contextMeta) {
|
|
|
13151
13597
|
return snapshot;
|
|
13152
13598
|
}
|
|
13153
13599
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
13154
|
-
const globalRoot =
|
|
13600
|
+
const globalRoot = path17.dirname(filePath);
|
|
13155
13601
|
await backupConfigFile(filePath, { globalRoot });
|
|
13156
13602
|
let raw;
|
|
13157
13603
|
try {
|
|
@@ -13631,7 +14077,7 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
13631
14077
|
|
|
13632
14078
|
// src/server/embedded-host-adapters.ts
|
|
13633
14079
|
import * as fs15 from "node:fs/promises";
|
|
13634
|
-
import * as
|
|
14080
|
+
import * as path19 from "node:path";
|
|
13635
14081
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
13636
14082
|
import { DefaultSessionStore as DefaultSessionStore2 } from "@wrongstack/core/storage";
|
|
13637
14083
|
import { toErrorMessage as toErrorMessage7, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
|
|
@@ -13639,7 +14085,7 @@ import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
|
13639
14085
|
|
|
13640
14086
|
// src/server/project-handlers.ts
|
|
13641
14087
|
import * as fs13 from "node:fs/promises";
|
|
13642
|
-
import * as
|
|
14088
|
+
import * as path18 from "node:path";
|
|
13643
14089
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
13644
14090
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
13645
14091
|
function createProjectHandlers(ctx) {
|
|
@@ -13691,8 +14137,8 @@ function createProjectHandlers(ctx) {
|
|
|
13691
14137
|
});
|
|
13692
14138
|
return;
|
|
13693
14139
|
}
|
|
13694
|
-
const resolved =
|
|
13695
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14140
|
+
const resolved = path18.resolve(parsed.value.root);
|
|
14141
|
+
const name2 = parsed.value.name?.trim() || path18.basename(resolved);
|
|
13696
14142
|
try {
|
|
13697
14143
|
const stat3 = await fs13.stat(resolved).catch(() => null);
|
|
13698
14144
|
if (!stat3?.isDirectory()) {
|
|
@@ -13703,7 +14149,7 @@ function createProjectHandlers(ctx) {
|
|
|
13703
14149
|
return;
|
|
13704
14150
|
}
|
|
13705
14151
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
13706
|
-
const already = before.projects.some((project) =>
|
|
14152
|
+
const already = before.projects.some((project) => path18.resolve(project.root) === resolved);
|
|
13707
14153
|
const entry = await touchProjectInManifest(
|
|
13708
14154
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
13709
14155
|
ctx.globalConfigPath
|
|
@@ -13733,8 +14179,8 @@ function createProjectHandlers(ctx) {
|
|
|
13733
14179
|
});
|
|
13734
14180
|
return;
|
|
13735
14181
|
}
|
|
13736
|
-
const resolved =
|
|
13737
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14182
|
+
const resolved = path18.resolve(parsed.value.root);
|
|
14183
|
+
const name2 = parsed.value.name?.trim() || path18.basename(resolved);
|
|
13738
14184
|
if (!ctx.allowProjectMutations) {
|
|
13739
14185
|
sendTo(ws, {
|
|
13740
14186
|
type: "projects.selected",
|
|
@@ -13769,6 +14215,17 @@ function createProjectHandlers(ctx) {
|
|
|
13769
14215
|
});
|
|
13770
14216
|
const previous = ctx.getSession();
|
|
13771
14217
|
const previousId = previous.id;
|
|
14218
|
+
const previousProjectRoot = ctx.getProjectRoot();
|
|
14219
|
+
const previousPaths = resolveWstackPaths4({
|
|
14220
|
+
projectRoot: previousProjectRoot,
|
|
14221
|
+
globalRoot: ctx.wpaths.globalRoot
|
|
14222
|
+
});
|
|
14223
|
+
const previousIdentityTarget = {
|
|
14224
|
+
projectSlug: previousPaths.projectSlug,
|
|
14225
|
+
projectRoot: previousProjectRoot,
|
|
14226
|
+
projectName: path18.basename(previousProjectRoot),
|
|
14227
|
+
workingDir: ctx.context.workingDir
|
|
14228
|
+
};
|
|
13772
14229
|
const previousUsage = ctx.tokenCounter.total();
|
|
13773
14230
|
const config = ctx.getConfig?.() ?? ctx.config;
|
|
13774
14231
|
const next = await store.create({
|
|
@@ -13794,7 +14251,16 @@ function createProjectHandlers(ctx) {
|
|
|
13794
14251
|
};
|
|
13795
14252
|
try {
|
|
13796
14253
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
14254
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
13797
14255
|
} catch (err) {
|
|
14256
|
+
try {
|
|
14257
|
+
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
14258
|
+
} catch {
|
|
14259
|
+
}
|
|
14260
|
+
try {
|
|
14261
|
+
await ctx.onSessionSwapped?.(previous.id, previousIdentityTarget);
|
|
14262
|
+
} catch {
|
|
14263
|
+
}
|
|
13798
14264
|
await next.close().catch(() => void 0);
|
|
13799
14265
|
await store.delete(next.id).catch(() => void 0);
|
|
13800
14266
|
throw err;
|
|
@@ -14880,6 +15346,7 @@ var CLIENT_WORKSPACE_MESSAGE_TYPES = [
|
|
|
14880
15346
|
var CLIENT_CONFIGURATION_MESSAGE_TYPES = [
|
|
14881
15347
|
"codebase.index.server.shutdown",
|
|
14882
15348
|
"connections.health",
|
|
15349
|
+
"connections.service_action",
|
|
14883
15350
|
"diag.get",
|
|
14884
15351
|
"key.add",
|
|
14885
15352
|
"key.delete",
|
|
@@ -15153,6 +15620,7 @@ var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
|
15153
15620
|
"codebase.index.server.shutdown_result",
|
|
15154
15621
|
"connections.health_error",
|
|
15155
15622
|
"connections.health_result",
|
|
15623
|
+
"connections.service_action_result",
|
|
15156
15624
|
"diag.get",
|
|
15157
15625
|
"key.operation_result",
|
|
15158
15626
|
"model.switch_result",
|
|
@@ -15197,13 +15665,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
15197
15665
|
// src/protocol/decoder.ts
|
|
15198
15666
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
15199
15667
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
15200
|
-
function inspectValue(value,
|
|
15668
|
+
function inspectValue(value, path34, depth) {
|
|
15201
15669
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
15202
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
15670
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path34 };
|
|
15203
15671
|
}
|
|
15204
15672
|
if (value === null || typeof value !== "object") return null;
|
|
15205
15673
|
for (const key of Object.keys(value)) {
|
|
15206
|
-
const childPath = `${
|
|
15674
|
+
const childPath = `${path34}.${key}`;
|
|
15207
15675
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
15208
15676
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
15209
15677
|
}
|
|
@@ -15344,7 +15812,8 @@ function projectToolMessage(message) {
|
|
|
15344
15812
|
name: text(payload["name"]),
|
|
15345
15813
|
ok: payload["ok"] !== false,
|
|
15346
15814
|
durationMs: finite(payload["durationMs"]),
|
|
15347
|
-
...typeof payload["output"] === "string" ? { output: payload["output"] } : {}
|
|
15815
|
+
...typeof payload["output"] === "string" ? { output: payload["output"] } : {},
|
|
15816
|
+
...Array.isArray(payload["sage"]) ? { sage: payload["sage"].filter((line) => typeof line === "string") } : {}
|
|
15348
15817
|
};
|
|
15349
15818
|
}
|
|
15350
15819
|
return null;
|
|
@@ -15584,6 +16053,7 @@ function createSessionHandlers(ctx) {
|
|
|
15584
16053
|
ctx.context.session = next;
|
|
15585
16054
|
ctx.context.state.replaceMessages(messages);
|
|
15586
16055
|
await ctx.context.flushConversationJournal?.();
|
|
16056
|
+
await ctx.onBeforeSessionTodosReplaced?.(next.id, sessionsDirectory());
|
|
15587
16057
|
ctx.context.state.replaceTodos(todos);
|
|
15588
16058
|
resetContextAccounting();
|
|
15589
16059
|
ctx.context.readFiles.clear();
|
|
@@ -15980,7 +16450,10 @@ function createSessionHandlers(ctx) {
|
|
|
15980
16450
|
rollbackClaim = await ctx.claimSession?.(canonicalId);
|
|
15981
16451
|
const resumed = await store.resume(canonicalId);
|
|
15982
16452
|
const restoredTodos = await loadTodosCheckpoint(
|
|
15983
|
-
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json")
|
|
16453
|
+
sessionScopedPath(sessionsDirectory(), resumed.writer.id, ".todos.json"),
|
|
16454
|
+
ctx.events,
|
|
16455
|
+
ctx.context.traceId,
|
|
16456
|
+
resumed.writer.id
|
|
15984
16457
|
).catch(() => null) ?? [];
|
|
15985
16458
|
activated = true;
|
|
15986
16459
|
await activateSession(
|
|
@@ -16132,7 +16605,7 @@ function createEmbeddedConversationRoutes(ctx) {
|
|
|
16132
16605
|
}
|
|
16133
16606
|
function sessionStoreFor(opts) {
|
|
16134
16607
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
16135
|
-
return opts.sessionStore ?? new DefaultSessionStore2({ dir:
|
|
16608
|
+
return opts.sessionStore ?? new DefaultSessionStore2({ dir: path19.join(projectRoot, ".wrongstack", "sessions"), projectRoot });
|
|
16136
16609
|
}
|
|
16137
16610
|
function createEmbeddedSessionRoutes(ctx) {
|
|
16138
16611
|
const { opts } = ctx;
|
|
@@ -16142,6 +16615,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16142
16615
|
config: { model: actx.model ?? "", provider: actx.provider?.id ?? "" },
|
|
16143
16616
|
getConfig: () => ({ model: actx.model ?? "", provider: actx.provider?.id ?? "" }),
|
|
16144
16617
|
context: actx,
|
|
16618
|
+
events: opts.events,
|
|
16145
16619
|
listTools: () => opts.agent.tools.list(),
|
|
16146
16620
|
getCompactor: () => opts.agent.container.resolve(TOKENS.Compactor),
|
|
16147
16621
|
getCustomModeStore: ctx.getCustomModeStore,
|
|
@@ -16150,11 +16624,12 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16150
16624
|
getSession: () => actx.session ?? opts.session,
|
|
16151
16625
|
getSessionStore: () => sessionStoreFor(opts),
|
|
16152
16626
|
canSwapSessions: () => opts.sessionStore !== void 0,
|
|
16153
|
-
getSessionsDir: () => opts.sessionsDir ??
|
|
16627
|
+
getSessionsDir: () => opts.sessionsDir ?? path19.join(getProjectRoot(), ".wrongstack", "sessions"),
|
|
16154
16628
|
setSession: (next) => {
|
|
16155
16629
|
actx.session = next;
|
|
16156
16630
|
},
|
|
16157
16631
|
claimSession: opts.claimSession,
|
|
16632
|
+
onBeforeSessionTodosReplaced: async (sessionId, sessionsDir) => opts.onBeforeSessionTodosReplaced?.(sessionId, sessionsDir),
|
|
16158
16633
|
onSessionSwapped: async (sessionId, target) => opts.onSessionSwapped?.(sessionId, target),
|
|
16159
16634
|
abortActiveRun: ctx.abortActiveRun,
|
|
16160
16635
|
isRunActive: ctx.isRunActive,
|
|
@@ -16166,7 +16641,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16166
16641
|
async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
16167
16642
|
const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
16168
16643
|
try {
|
|
16169
|
-
const raw = await fs15.readFile(
|
|
16644
|
+
const raw = await fs15.readFile(path19.join(projectRoot, ".wrongstack", "goal.json"), "utf8");
|
|
16170
16645
|
ctx.broadcast({ type: "goal-state.updated", payload: JSON.parse(raw) });
|
|
16171
16646
|
} catch {
|
|
16172
16647
|
ctx.broadcast({ type: "goal-state.updated", payload: null });
|
|
@@ -16175,10 +16650,10 @@ async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
|
16175
16650
|
function createEmbeddedProjectRoutes(ctx) {
|
|
16176
16651
|
const { opts } = ctx;
|
|
16177
16652
|
const actx = opts.agent.ctx;
|
|
16178
|
-
const globalConfigPath = opts.globalConfigPath ??
|
|
16653
|
+
const globalConfigPath = opts.globalConfigPath ?? path19.join(wstackGlobalRoot2(), "config.json");
|
|
16179
16654
|
return createProjectHandlers({
|
|
16180
16655
|
globalConfigPath,
|
|
16181
|
-
wpaths: { globalRoot:
|
|
16656
|
+
wpaths: { globalRoot: path19.dirname(globalConfigPath) },
|
|
16182
16657
|
context: actx,
|
|
16183
16658
|
tokenCounter: actx.tokenCounter,
|
|
16184
16659
|
config: { model: actx.model, provider: actx.provider.id },
|
|
@@ -16203,6 +16678,7 @@ function createEmbeddedProjectRoutes(ctx) {
|
|
|
16203
16678
|
for (const controller of ctx.abortControllers.values()) controller.abort();
|
|
16204
16679
|
ctx.abortControllers.clear();
|
|
16205
16680
|
},
|
|
16681
|
+
onBeforeSessionTodosReplaced: async (sessionId, sessionsDir) => opts.onBeforeSessionTodosReplaced?.(sessionId, sessionsDir),
|
|
16206
16682
|
onSessionSwapped: async (sessionId, target) => opts.onSessionSwapped?.(sessionId, target),
|
|
16207
16683
|
allowProjectMutations: true,
|
|
16208
16684
|
sessionStartPayload: ctx.buildSessionStart,
|
|
@@ -16658,7 +17134,7 @@ ${String(p.content ?? "")}`;
|
|
|
16658
17134
|
};
|
|
16659
17135
|
|
|
16660
17136
|
// src/server/codebase-index-server-control.ts
|
|
16661
|
-
import { shutdownCodebaseIndexServer } from "@wrongstack/tools";
|
|
17137
|
+
import { shutdownCodebaseIndexServer as shutdownCodebaseIndexServer2 } from "@wrongstack/tools";
|
|
16662
17138
|
async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
16663
17139
|
if (message.type !== "codebase.index.server.shutdown") return false;
|
|
16664
17140
|
const requestId = message.payload && typeof message.payload === "object" && typeof message.payload.requestId === "string" ? message.payload.requestId : "";
|
|
@@ -16685,7 +17161,7 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
16685
17161
|
});
|
|
16686
17162
|
return true;
|
|
16687
17163
|
}
|
|
16688
|
-
const result = await
|
|
17164
|
+
const result = await shutdownCodebaseIndexServer2(
|
|
16689
17165
|
projectRoot,
|
|
16690
17166
|
deps2.getIndexDir(),
|
|
16691
17167
|
"websocket-request"
|
|
@@ -17226,7 +17702,7 @@ function createRouteFamilyDispatcher(options) {
|
|
|
17226
17702
|
|
|
17227
17703
|
// src/server/shell-open.ts
|
|
17228
17704
|
import * as fs16 from "node:fs/promises";
|
|
17229
|
-
import * as
|
|
17705
|
+
import * as path20 from "node:path";
|
|
17230
17706
|
import { spawn as spawn3 } from "node:child_process";
|
|
17231
17707
|
function normalizeShellOpenTarget(target) {
|
|
17232
17708
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -17237,11 +17713,11 @@ function shellQuote(s) {
|
|
|
17237
17713
|
}
|
|
17238
17714
|
async function handleShellOpen(req, logger, options) {
|
|
17239
17715
|
try {
|
|
17240
|
-
const resolved =
|
|
17716
|
+
const resolved = path20.resolve(req.path);
|
|
17241
17717
|
if (options?.projectRoot) {
|
|
17242
|
-
const root =
|
|
17243
|
-
const relative5 =
|
|
17244
|
-
const escapes = relative5.startsWith("..") ||
|
|
17718
|
+
const root = path20.resolve(options.projectRoot);
|
|
17719
|
+
const relative5 = path20.relative(root, resolved);
|
|
17720
|
+
const escapes = relative5.startsWith("..") || path20.isAbsolute(relative5);
|
|
17245
17721
|
if (escapes) {
|
|
17246
17722
|
return {
|
|
17247
17723
|
success: false,
|
|
@@ -17632,6 +18108,13 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
17632
18108
|
message
|
|
17633
18109
|
))
|
|
17634
18110
|
return;
|
|
18111
|
+
if (await handleConnectionsServiceAction(ws, message, {
|
|
18112
|
+
getProjectRoot: projectRoot,
|
|
18113
|
+
getIndexDir: () => typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
|
|
18114
|
+
send: send2,
|
|
18115
|
+
backend: "cli-embedded"
|
|
18116
|
+
}))
|
|
18117
|
+
return;
|
|
17635
18118
|
if (await handleCodebaseIndexServerControl(ws, message, {
|
|
17636
18119
|
trustBoundary: deps2.trustBoundary,
|
|
17637
18120
|
logger: deps2.logger,
|
|
@@ -17646,7 +18129,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
17646
18129
|
}
|
|
17647
18130
|
|
|
17648
18131
|
// src/server/provider-config-standalone.ts
|
|
17649
|
-
import * as
|
|
18132
|
+
import * as path21 from "node:path";
|
|
17650
18133
|
import { DefaultSecretVault } from "@wrongstack/core/security";
|
|
17651
18134
|
function createProviderConfigIO(configPath) {
|
|
17652
18135
|
const keyFile = vaultKeyFileForConfigPath(configPath);
|
|
@@ -17657,10 +18140,10 @@ function createProviderConfigIO(configPath) {
|
|
|
17657
18140
|
};
|
|
17658
18141
|
}
|
|
17659
18142
|
function vaultKeyFileForConfigPath(configPath) {
|
|
17660
|
-
const configDir =
|
|
17661
|
-
const parentDir =
|
|
17662
|
-
const globalRoot =
|
|
17663
|
-
return
|
|
18143
|
+
const configDir = path21.dirname(configPath);
|
|
18144
|
+
const parentDir = path21.dirname(configDir);
|
|
18145
|
+
const globalRoot = path21.basename(parentDir) === "profiles" ? path21.dirname(parentDir) : configDir;
|
|
18146
|
+
return path21.join(globalRoot, ".key");
|
|
17664
18147
|
}
|
|
17665
18148
|
|
|
17666
18149
|
// src/server/provider-store.ts
|
|
@@ -17677,8 +18160,8 @@ function createConfigWriteLock() {
|
|
|
17677
18160
|
acquire() {
|
|
17678
18161
|
const prev = lock;
|
|
17679
18162
|
let release = () => void 0;
|
|
17680
|
-
lock = new Promise((
|
|
17681
|
-
release =
|
|
18163
|
+
lock = new Promise((resolve16) => {
|
|
18164
|
+
release = resolve16;
|
|
17682
18165
|
});
|
|
17683
18166
|
return { prev, release };
|
|
17684
18167
|
}
|
|
@@ -17756,6 +18239,7 @@ function createProviderStore(deps2) {
|
|
|
17756
18239
|
import { listBoards as listBoards5 } from "@wrongstack/kanban";
|
|
17757
18240
|
import {
|
|
17758
18241
|
applySddLifecycle,
|
|
18242
|
+
extractVerificationCommand,
|
|
17759
18243
|
SddBoardStore
|
|
17760
18244
|
} from "@wrongstack/sdd";
|
|
17761
18245
|
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -17778,14 +18262,16 @@ var SddBoardWebSocketHandler = class {
|
|
|
17778
18262
|
store;
|
|
17779
18263
|
clients = /* @__PURE__ */ new Set();
|
|
17780
18264
|
lifecycle;
|
|
18265
|
+
security;
|
|
17781
18266
|
diskPollingEnabled;
|
|
17782
18267
|
latest = null;
|
|
17783
18268
|
poll = null;
|
|
17784
18269
|
pollInFlight = false;
|
|
17785
18270
|
unsub = null;
|
|
17786
|
-
constructor(boardsDir, events, lifecycle) {
|
|
18271
|
+
constructor(boardsDir, events, lifecycle, security) {
|
|
17787
18272
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
17788
18273
|
this.lifecycle = lifecycle;
|
|
18274
|
+
this.security = security;
|
|
17789
18275
|
this.diskPollingEnabled = events === void 0;
|
|
17790
18276
|
if (events) {
|
|
17791
18277
|
const handler = (e) => {
|
|
@@ -17831,6 +18317,43 @@ var SddBoardWebSocketHandler = class {
|
|
|
17831
18317
|
return;
|
|
17832
18318
|
}
|
|
17833
18319
|
if (CONTROL_TYPES.has(action)) {
|
|
18320
|
+
const verificationCommands = [];
|
|
18321
|
+
if (action === "set_task_verification") {
|
|
18322
|
+
const command = msg.payload?.verificationCommand;
|
|
18323
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192)) return;
|
|
18324
|
+
if (typeof command === "string" && command.trim()) {
|
|
18325
|
+
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
18326
|
+
}
|
|
18327
|
+
} else if (action === "split_task") {
|
|
18328
|
+
const subtasks = msg.payload?.subtasks;
|
|
18329
|
+
if (Array.isArray(subtasks)) {
|
|
18330
|
+
for (const subtask of subtasks) {
|
|
18331
|
+
if (!subtask || typeof subtask !== "object") continue;
|
|
18332
|
+
const criterion = subtask.successCriterion;
|
|
18333
|
+
if (criterion === void 0) continue;
|
|
18334
|
+
if (typeof criterion !== "string") return;
|
|
18335
|
+
const command = extractVerificationCommand([criterion]);
|
|
18336
|
+
if (!command) continue;
|
|
18337
|
+
if (command.length > 8192) return;
|
|
18338
|
+
verificationCommands.push({ command, operation: "sdd.split_task_verification" });
|
|
18339
|
+
}
|
|
18340
|
+
}
|
|
18341
|
+
}
|
|
18342
|
+
for (const { command, operation } of verificationCommands) {
|
|
18343
|
+
if (!this.security) return;
|
|
18344
|
+
const authorization = await authorizeWebUIAction(
|
|
18345
|
+
this.security.trustBoundary,
|
|
18346
|
+
{
|
|
18347
|
+
capability: "process.spawn",
|
|
18348
|
+
subject: { kind: "command", id: command },
|
|
18349
|
+
risk: "high",
|
|
18350
|
+
cwd: this.lifecycle?.projectRoot,
|
|
18351
|
+
metadata: { operation }
|
|
18352
|
+
},
|
|
18353
|
+
this.security.logger
|
|
18354
|
+
);
|
|
18355
|
+
if (!authorization.allowed) return;
|
|
18356
|
+
}
|
|
17834
18357
|
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
17835
18358
|
if (runId) {
|
|
17836
18359
|
await this.store.appendControl(runId, {
|
|
@@ -17943,7 +18466,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
17943
18466
|
};
|
|
17944
18467
|
|
|
17945
18468
|
// src/server/sdd-wizard-wiring.ts
|
|
17946
|
-
import * as
|
|
18469
|
+
import * as path22 from "node:path";
|
|
17947
18470
|
import {
|
|
17948
18471
|
DefaultTaskStore,
|
|
17949
18472
|
TaskTracker
|
|
@@ -18057,7 +18580,7 @@ function buildSddWizardDeps(opts) {
|
|
|
18057
18580
|
}).catch(() => {
|
|
18058
18581
|
projectContext = "";
|
|
18059
18582
|
});
|
|
18060
|
-
const sessionPath = opts.paths.projectSddSession ??
|
|
18583
|
+
const sessionPath = opts.paths.projectSddSession ?? path22.join(opts.paths.projectDir, "sdd-session.json");
|
|
18061
18584
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
18062
18585
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
18063
18586
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -18339,7 +18862,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
18339
18862
|
return;
|
|
18340
18863
|
}
|
|
18341
18864
|
const { runId } = await this.deps.startRun(this.driver, opts);
|
|
18342
|
-
this.driver.setLastRunId(runId);
|
|
18865
|
+
await this.driver.setLastRunId(runId);
|
|
18343
18866
|
if (this.driver.phase() !== "executing" && this.driver.phase() !== "done") {
|
|
18344
18867
|
try {
|
|
18345
18868
|
if (this.driver.phase() === "task_review") await this.driver.approve();
|
|
@@ -18398,7 +18921,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
18398
18921
|
this.lastAgentText = text2;
|
|
18399
18922
|
if (this.driver) {
|
|
18400
18923
|
await this.driver.ingestAgentOutput(text2);
|
|
18401
|
-
this.driver.setLastAgentText(text2);
|
|
18924
|
+
await this.driver.setLastAgentText(text2);
|
|
18402
18925
|
}
|
|
18403
18926
|
this.broadcast({ type: "sdd.spec.agent_text", payload: { text: text2 } });
|
|
18404
18927
|
} finally {
|
|
@@ -18429,10 +18952,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
18429
18952
|
|
|
18430
18953
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
18431
18954
|
import { watch as fsWatch } from "node:fs";
|
|
18432
|
-
import * as
|
|
18955
|
+
import * as path23 from "node:path";
|
|
18433
18956
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
18434
18957
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
18435
|
-
const globalRoot = globalConfigPath ?
|
|
18958
|
+
const globalRoot = globalConfigPath ? path23.dirname(globalConfigPath) : void 0;
|
|
18436
18959
|
if (!globalRoot) return void 0;
|
|
18437
18960
|
const disposers = [];
|
|
18438
18961
|
const broadcastSessions = async () => {
|
|
@@ -18442,8 +18965,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
18442
18965
|
const sessions = await registry.list();
|
|
18443
18966
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
18444
18967
|
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 :
|
|
18968
|
+
const myRoot = path23.resolve(context.projectRoot);
|
|
18969
|
+
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
18970
|
sessionId: s.sessionId,
|
|
18448
18971
|
projectName: s.projectName,
|
|
18449
18972
|
projectSlug: s.projectSlug,
|
|
@@ -18634,13 +19157,13 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
18634
19157
|
// src/server/setup-events-status-watcher.ts
|
|
18635
19158
|
import { watch as fsWatch2 } from "node:fs";
|
|
18636
19159
|
import * as fs18 from "node:fs/promises";
|
|
18637
|
-
import * as
|
|
19160
|
+
import * as path25 from "node:path";
|
|
18638
19161
|
|
|
18639
19162
|
// src/server/setup-events-watcher.ts
|
|
18640
|
-
import * as
|
|
19163
|
+
import * as path24 from "node:path";
|
|
18641
19164
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
18642
19165
|
const raw = String(filename);
|
|
18643
|
-
const relative5 =
|
|
19166
|
+
const relative5 = path24.isAbsolute(raw) ? path24.relative(projectsDir, raw) : raw;
|
|
18644
19167
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
18645
19168
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
18646
19169
|
return parts.at(-2) ?? null;
|
|
@@ -18675,7 +19198,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
18675
19198
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
18676
19199
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
18677
19200
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
18678
|
-
const projectsDir =
|
|
19201
|
+
const projectsDir = path25.join(wpaths.globalRoot, "projects");
|
|
18679
19202
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
18680
19203
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
18681
19204
|
const DEBOUNCE_MS2 = 150;
|
|
@@ -18734,7 +19257,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
18734
19257
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
18735
19258
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
18736
19259
|
try {
|
|
18737
|
-
const targetFile =
|
|
19260
|
+
const targetFile = path25.join(projectsDir, projectHash, "status.json");
|
|
18738
19261
|
const content = await fs18.readFile(targetFile, "utf-8");
|
|
18739
19262
|
const statusData = JSON.parse(content);
|
|
18740
19263
|
scheduleBroadcast(projectHash, statusData);
|
|
@@ -18793,7 +19316,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
18793
19316
|
|
|
18794
19317
|
// src/server/setup-events-core-watchers.ts
|
|
18795
19318
|
import * as fs19 from "node:fs/promises";
|
|
18796
|
-
import * as
|
|
19319
|
+
import * as path26 from "node:path";
|
|
18797
19320
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
18798
19321
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
18799
19322
|
const disposers = [];
|
|
@@ -18829,7 +19352,7 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
18829
19352
|
if (wpaths?.projectStatus) {
|
|
18830
19353
|
try {
|
|
18831
19354
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
18832
|
-
const dir =
|
|
19355
|
+
const dir = path26.dirname(statusFile);
|
|
18833
19356
|
await fs19.mkdir(dir, { recursive: true });
|
|
18834
19357
|
await fs19.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
18835
19358
|
} catch (err) {
|
|
@@ -19019,6 +19542,9 @@ function setupEvents(deps2) {
|
|
|
19019
19542
|
input: scrub(e.input),
|
|
19020
19543
|
fileTargets: extractCodeMapFileTargets(projectRoot || ".", e.name, e.input),
|
|
19021
19544
|
output: scrub(e.output),
|
|
19545
|
+
// SAGE-injected memory rides beside the tool text so the client renders
|
|
19546
|
+
// it as a memory card. Never folded back into `output`.
|
|
19547
|
+
...e.sage && e.sage.length > 0 ? { sage: e.sage.map((line) => scrub(line)) } : {},
|
|
19022
19548
|
outputBytes: e.outputBytes,
|
|
19023
19549
|
outputTokens: e.outputTokens,
|
|
19024
19550
|
outputLines: e.outputLines,
|
|
@@ -19853,17 +20379,24 @@ var SpecsWebSocketHandler = class {
|
|
|
19853
20379
|
// src/server/start-webui.ts
|
|
19854
20380
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
19855
20381
|
import * as http2 from "node:http";
|
|
19856
|
-
import * as
|
|
20382
|
+
import * as path33 from "node:path";
|
|
19857
20383
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
19858
20384
|
import { getSharedProjectMailbox as getSharedProjectMailbox5, resolveProjectDir as resolveProjectDir4 } from "@wrongstack/core/coordination";
|
|
19859
20385
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
19860
20386
|
import {
|
|
20387
|
+
attachTodosCheckpoint,
|
|
19861
20388
|
createSessionEventBridge,
|
|
19862
20389
|
resolveSessionLoggingConfig,
|
|
19863
20390
|
watchProviderConfig
|
|
19864
20391
|
} from "@wrongstack/core/storage";
|
|
19865
20392
|
import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } from "@wrongstack/core/types";
|
|
19866
|
-
import {
|
|
20393
|
+
import {
|
|
20394
|
+
expectDefined as expectDefined4,
|
|
20395
|
+
sessionScopedPath as sessionScopedPath3,
|
|
20396
|
+
startHeapWatchdog,
|
|
20397
|
+
toErrorMessage as toErrorMessage14,
|
|
20398
|
+
wstackGlobalRoot as wstackGlobalRoot4
|
|
20399
|
+
} from "@wrongstack/core/utils";
|
|
19867
20400
|
import { makeProviderFromConfig as makeProviderFromConfig5 } from "@wrongstack/providers";
|
|
19868
20401
|
import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
19869
20402
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
@@ -20050,7 +20583,7 @@ function findWorkspaceCliEntry(projectRoot) {
|
|
|
20050
20583
|
return null;
|
|
20051
20584
|
}
|
|
20052
20585
|
function sleep(ms) {
|
|
20053
|
-
return new Promise((
|
|
20586
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
20054
20587
|
}
|
|
20055
20588
|
|
|
20056
20589
|
// src/server/terminal-ws-handler.ts
|
|
@@ -20286,7 +20819,7 @@ function clampDim(value, fallback) {
|
|
|
20286
20819
|
}
|
|
20287
20820
|
|
|
20288
20821
|
// src/server/worktree-ws-handler.ts
|
|
20289
|
-
import { join as join14, resolve as
|
|
20822
|
+
import { join as join14, resolve as resolve13, sep as sep5 } from "node:path";
|
|
20290
20823
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
20291
20824
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
20292
20825
|
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
@@ -20347,13 +20880,13 @@ var WorktreeWebSocketHandler = class {
|
|
|
20347
20880
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
20348
20881
|
/** Absolute managed-worktrees root for this project. */
|
|
20349
20882
|
worktreesRoot() {
|
|
20350
|
-
return
|
|
20883
|
+
return resolve13(join14(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
20351
20884
|
}
|
|
20352
20885
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
20353
20886
|
underRoot(dir) {
|
|
20354
|
-
const abs =
|
|
20887
|
+
const abs = resolve13(dir);
|
|
20355
20888
|
const root = this.worktreesRoot();
|
|
20356
|
-
return abs !== root && abs.startsWith(root +
|
|
20889
|
+
return abs !== root && abs.startsWith(root + sep5);
|
|
20357
20890
|
}
|
|
20358
20891
|
/** Branches of worktrees a live in-session run currently owns. */
|
|
20359
20892
|
liveActiveBranches() {
|
|
@@ -20501,7 +21034,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
20501
21034
|
}
|
|
20502
21035
|
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
20503
21036
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
20504
|
-
const summary = await wt.diffSummary(
|
|
21037
|
+
const summary = await wt.diffSummary(resolve13(dir), base);
|
|
20505
21038
|
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
20506
21039
|
}
|
|
20507
21040
|
// ── internals ───────────────────────────────────────────────────────────
|
|
@@ -20650,7 +21183,9 @@ async function createAgentServices(input) {
|
|
|
20650
21183
|
memory: memoryRetrieval,
|
|
20651
21184
|
maxHintsPerTool: config.Sage?.inject?.maxHintsPerTool,
|
|
20652
21185
|
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
21186
|
+
taskAware: config.Sage?.inject?.taskAware,
|
|
20653
21187
|
minScore: config.Sage?.inject?.minScore,
|
|
21188
|
+
minImportance: config.Sage?.inject?.minImportance,
|
|
20654
21189
|
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
20655
21190
|
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
20656
21191
|
triggers: config.Sage?.inject?.triggers
|
|
@@ -20953,15 +21488,20 @@ async function createAgentServices(input) {
|
|
|
20953
21488
|
projectRoot
|
|
20954
21489
|
);
|
|
20955
21490
|
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
20956
|
-
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
|
|
21491
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
21492
|
+
wpaths.projectSddBoards,
|
|
21493
|
+
void 0,
|
|
21494
|
+
{
|
|
21495
|
+
projectRoot,
|
|
21496
|
+
paths: {
|
|
21497
|
+
projectSpecs: wpaths.projectSpecs,
|
|
21498
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
21499
|
+
projectSddSession: wpaths.projectSddSession,
|
|
21500
|
+
projectSddBoards: wpaths.projectSddBoards
|
|
21501
|
+
}
|
|
21502
|
+
},
|
|
21503
|
+
{ trustBoundary: input.trustBoundary, logger }
|
|
21504
|
+
);
|
|
20965
21505
|
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
20966
21506
|
buildSddWizardDeps({
|
|
20967
21507
|
agent,
|
|
@@ -20973,7 +21513,16 @@ async function createAgentServices(input) {
|
|
|
20973
21513
|
providerRegistry,
|
|
20974
21514
|
toolRegistry,
|
|
20975
21515
|
session: input.sessionGetter(),
|
|
20976
|
-
projectRoot
|
|
21516
|
+
projectRoot,
|
|
21517
|
+
// Thread the container-provided ProviderModelStatusTracker so a 429
|
|
21518
|
+
// from this subagent's first call transitions the (provider, model)
|
|
21519
|
+
// pair to `state: 'blocked'` instead of silently no-op'ing. The
|
|
21520
|
+
// runtime container binds a default `ProviderModelStatusTracker`
|
|
21521
|
+
// (see packages/runtime/src/container.ts); without this dep, the
|
|
21522
|
+
// subagent's fallback extension's tracker hooks are undefined and
|
|
21523
|
+
// round-robin keeps reassigning the doomed model. Mirrors the CLI
|
|
21524
|
+
// factory wiring at host-subagent-factory.ts:337.
|
|
21525
|
+
statusTracker: container.safeResolve(TOKENS2.ProviderModelStatusTracker)
|
|
20977
21526
|
}),
|
|
20978
21527
|
paths: {
|
|
20979
21528
|
projectSpecs: wpaths.projectSpecs,
|
|
@@ -21116,7 +21665,7 @@ function createConnectionHandler(options) {
|
|
|
21116
21665
|
}
|
|
21117
21666
|
|
|
21118
21667
|
// src/server/message-dispatcher.ts
|
|
21119
|
-
import
|
|
21668
|
+
import path27 from "node:path";
|
|
21120
21669
|
function createMessageDispatcher(opts) {
|
|
21121
21670
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
21122
21671
|
function makeWorklistContext() {
|
|
@@ -21137,7 +21686,7 @@ function createMessageDispatcher(opts) {
|
|
|
21137
21686
|
skillLoader: deps2.skillLoader,
|
|
21138
21687
|
skillInstaller: deps2.skillInstaller,
|
|
21139
21688
|
projectRoot,
|
|
21140
|
-
projectSkillsDir:
|
|
21689
|
+
projectSkillsDir: path27.join(projectRoot, ".wrongstack", "skills"),
|
|
21141
21690
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
21142
21691
|
};
|
|
21143
21692
|
}
|
|
@@ -21389,7 +21938,7 @@ function createMessageDispatcher(opts) {
|
|
|
21389
21938
|
|
|
21390
21939
|
// src/server/pre-context-services.ts
|
|
21391
21940
|
import { createRequire as createRequire3 } from "node:module";
|
|
21392
|
-
import * as
|
|
21941
|
+
import * as path30 from "node:path";
|
|
21393
21942
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
21394
21943
|
import {
|
|
21395
21944
|
getSharedProjectMailbox as getSharedProjectMailbox4,
|
|
@@ -21444,7 +21993,7 @@ import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/too
|
|
|
21444
21993
|
|
|
21445
21994
|
// src/server/model-auto-discovery.ts
|
|
21446
21995
|
import * as fs20 from "node:fs/promises";
|
|
21447
|
-
import * as
|
|
21996
|
+
import * as path28 from "node:path";
|
|
21448
21997
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
21449
21998
|
function isOverlayRegistry(value) {
|
|
21450
21999
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -21480,7 +22029,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
21480
22029
|
if (!isOverlayRegistry(registry)) return;
|
|
21481
22030
|
const targets = eligibleProviders(opts.config);
|
|
21482
22031
|
if (targets.length === 0) return;
|
|
21483
|
-
const cacheFile =
|
|
22032
|
+
const cacheFile = path28.join(opts.cacheDir, "discovered-models-cache.json");
|
|
21484
22033
|
const cache2 = await readCache(cacheFile);
|
|
21485
22034
|
let cacheDirty = false;
|
|
21486
22035
|
await Promise.all(
|
|
@@ -21517,7 +22066,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
21517
22066
|
);
|
|
21518
22067
|
if (cacheDirty) {
|
|
21519
22068
|
try {
|
|
21520
|
-
await fs20.mkdir(
|
|
22069
|
+
await fs20.mkdir(path28.dirname(cacheFile), { recursive: true });
|
|
21521
22070
|
await fs20.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
21522
22071
|
} catch {
|
|
21523
22072
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
@@ -21614,7 +22163,7 @@ function resolveSetupProvider(opts) {
|
|
|
21614
22163
|
}
|
|
21615
22164
|
|
|
21616
22165
|
// src/server/standalone-session-identity.ts
|
|
21617
|
-
import * as
|
|
22166
|
+
import * as path29 from "node:path";
|
|
21618
22167
|
import {
|
|
21619
22168
|
AgentStatusTracker,
|
|
21620
22169
|
FleetNotifier,
|
|
@@ -21633,7 +22182,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
21633
22182
|
let activeTarget = {
|
|
21634
22183
|
projectSlug: paths.projectSlug,
|
|
21635
22184
|
projectRoot: paths.projectRoot,
|
|
21636
|
-
projectName:
|
|
22185
|
+
projectName: path29.basename(paths.projectRoot),
|
|
21637
22186
|
workingDir: opts.workingDir
|
|
21638
22187
|
};
|
|
21639
22188
|
let pendingClaim;
|
|
@@ -21864,7 +22413,7 @@ async function createPreContextServices(input) {
|
|
|
21864
22413
|
await discoverAndMergeWebuiProviders({
|
|
21865
22414
|
config,
|
|
21866
22415
|
registry: modelsRegistry,
|
|
21867
|
-
cacheDir:
|
|
22416
|
+
cacheDir: path30.dirname(wpaths.modelsCache),
|
|
21868
22417
|
logger
|
|
21869
22418
|
});
|
|
21870
22419
|
} catch (err) {
|
|
@@ -21916,7 +22465,7 @@ async function createPreContextServices(input) {
|
|
|
21916
22465
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
21917
22466
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
21918
22467
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
21919
|
-
|
|
22468
|
+
path30.join(wpaths.projectDir, "mcp-auth.json"),
|
|
21920
22469
|
vault
|
|
21921
22470
|
);
|
|
21922
22471
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -22022,7 +22571,7 @@ async function createPreContextServices(input) {
|
|
|
22022
22571
|
};
|
|
22023
22572
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
22024
22573
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
22025
|
-
manifestPath:
|
|
22574
|
+
manifestPath: path30.join(wpaths.configDir, "installed-skills.json"),
|
|
22026
22575
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
22027
22576
|
globalSkillsDir: wpaths.globalSkills,
|
|
22028
22577
|
projectHash: wpaths.projectHash,
|
|
@@ -22032,8 +22581,8 @@ async function createPreContextServices(input) {
|
|
|
22032
22581
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
22033
22582
|
try {
|
|
22034
22583
|
const req = createRequire3(import.meta.url);
|
|
22035
|
-
return
|
|
22036
|
-
|
|
22584
|
+
return path30.join(
|
|
22585
|
+
path30.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
22037
22586
|
"data",
|
|
22038
22587
|
"prompts"
|
|
22039
22588
|
);
|
|
@@ -22137,7 +22686,7 @@ async function createPreContextServices(input) {
|
|
|
22137
22686
|
}
|
|
22138
22687
|
|
|
22139
22688
|
// src/server/routes.ts
|
|
22140
|
-
import
|
|
22689
|
+
import path31 from "node:path";
|
|
22141
22690
|
import { makeProviderFromConfig as makeProviderFromConfig4, withCatalogCapabilities } from "@wrongstack/providers";
|
|
22142
22691
|
|
|
22143
22692
|
// src/server/mode-handlers.ts
|
|
@@ -22262,6 +22811,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22262
22811
|
config: state.getConfig(),
|
|
22263
22812
|
clients: state.getClients(),
|
|
22264
22813
|
context: deps2.context,
|
|
22814
|
+
events: deps2.events,
|
|
22265
22815
|
toolRegistry: deps2.toolRegistry,
|
|
22266
22816
|
compactor: deps2.compactor,
|
|
22267
22817
|
customModeStore: deps2.customModeStore,
|
|
@@ -22273,6 +22823,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22273
22823
|
setSession: state.setSession,
|
|
22274
22824
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
22275
22825
|
claimSession: cb.claimSession,
|
|
22826
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
22276
22827
|
onSessionSwapped: cb.onSessionSwapped,
|
|
22277
22828
|
abortActiveRun: state.abortRunLock,
|
|
22278
22829
|
isRunActive: state.isRunActive,
|
|
@@ -22294,6 +22845,8 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22294
22845
|
setSessionStore: state.setSessionStore,
|
|
22295
22846
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
22296
22847
|
abortRunLock: state.abortRunLock,
|
|
22848
|
+
onBeforeSessionTodosReplaced: cb.onBeforeSessionTodosReplaced,
|
|
22849
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
22297
22850
|
sessionStartPayload: cb.sessionStartPayload
|
|
22298
22851
|
});
|
|
22299
22852
|
const modeRoutes = createModeHandlers({
|
|
@@ -22425,7 +22978,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22425
22978
|
};
|
|
22426
22979
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
22427
22980
|
getProjectRoot: state.getProjectRoot,
|
|
22428
|
-
getGlobalRoot: () =>
|
|
22981
|
+
getGlobalRoot: () => path31.dirname(deps2.globalConfigPath),
|
|
22429
22982
|
events: deps2.events
|
|
22430
22983
|
});
|
|
22431
22984
|
const mcpRoutes = {
|
|
@@ -22498,7 +23051,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22498
23051
|
}
|
|
22499
23052
|
|
|
22500
23053
|
// src/server/server-runtime.ts
|
|
22501
|
-
import * as
|
|
23054
|
+
import * as path32 from "node:path";
|
|
22502
23055
|
import { createRequire as createRequire4 } from "node:module";
|
|
22503
23056
|
import { fileURLToPath } from "node:url";
|
|
22504
23057
|
import { WebSocketServer } from "ws";
|
|
@@ -22559,7 +23112,7 @@ function createSessionStartPayload(g) {
|
|
|
22559
23112
|
inputCost,
|
|
22560
23113
|
outputCost,
|
|
22561
23114
|
cacheReadCost,
|
|
22562
|
-
projectName:
|
|
23115
|
+
projectName: path32.basename(projectRoot) || projectRoot,
|
|
22563
23116
|
projectRoot,
|
|
22564
23117
|
cwd: g.getWorkingDir(),
|
|
22565
23118
|
mode: g.getModeId(),
|
|
@@ -22647,13 +23200,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
22647
23200
|
};
|
|
22648
23201
|
}
|
|
22649
23202
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
22650
|
-
if (explicitDistDir) return
|
|
23203
|
+
if (explicitDistDir) return path32.resolve(explicitDistDir);
|
|
22651
23204
|
try {
|
|
22652
23205
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
22653
23206
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
22654
|
-
return
|
|
23207
|
+
return path32.dirname(serverEntry);
|
|
22655
23208
|
} catch {
|
|
22656
|
-
return
|
|
23209
|
+
return path32.resolve(path32.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
22657
23210
|
}
|
|
22658
23211
|
}
|
|
22659
23212
|
function startHttpServer(opts) {
|
|
@@ -22684,6 +23237,56 @@ function registerShutdown(deps2) {
|
|
|
22684
23237
|
}
|
|
22685
23238
|
|
|
22686
23239
|
// src/server/start-webui.ts
|
|
23240
|
+
function createStandaloneTodosCheckpointLifecycle(input) {
|
|
23241
|
+
let checkpointSessionId = input.sessionId;
|
|
23242
|
+
let checkpointSessionsDir = input.sessionsDir;
|
|
23243
|
+
const attachCheckpoint = (sessionId, sessionsDir) => attachTodosCheckpoint(
|
|
23244
|
+
input.state,
|
|
23245
|
+
sessionScopedPath3(sessionsDir, sessionId, ".todos.json"),
|
|
23246
|
+
sessionId,
|
|
23247
|
+
input.events,
|
|
23248
|
+
input.traceId,
|
|
23249
|
+
input.warn
|
|
23250
|
+
);
|
|
23251
|
+
let detachCurrent = attachCheckpoint(input.sessionId, input.sessionsDir);
|
|
23252
|
+
let checkpointAttached = true;
|
|
23253
|
+
const detachCurrentCheckpoint = async () => {
|
|
23254
|
+
if (!checkpointAttached) return;
|
|
23255
|
+
checkpointAttached = false;
|
|
23256
|
+
await detachCurrent();
|
|
23257
|
+
};
|
|
23258
|
+
let transitionTail = Promise.resolve();
|
|
23259
|
+
const rebind = (nextSessionId, sessionsDir) => {
|
|
23260
|
+
const transition = transitionTail.then(async () => {
|
|
23261
|
+
if (checkpointAttached && nextSessionId === checkpointSessionId && sessionsDir === checkpointSessionsDir) {
|
|
23262
|
+
return;
|
|
23263
|
+
}
|
|
23264
|
+
let detachFailed = false;
|
|
23265
|
+
let detachError;
|
|
23266
|
+
try {
|
|
23267
|
+
await detachCurrentCheckpoint();
|
|
23268
|
+
} catch (error2) {
|
|
23269
|
+
detachFailed = true;
|
|
23270
|
+
detachError = error2;
|
|
23271
|
+
}
|
|
23272
|
+
const nextDetach = attachCheckpoint(nextSessionId, sessionsDir);
|
|
23273
|
+
checkpointSessionId = nextSessionId;
|
|
23274
|
+
checkpointSessionsDir = sessionsDir;
|
|
23275
|
+
detachCurrent = nextDetach;
|
|
23276
|
+
checkpointAttached = true;
|
|
23277
|
+
if (detachFailed) throw detachError;
|
|
23278
|
+
});
|
|
23279
|
+
transitionTail = transition.catch(() => void 0);
|
|
23280
|
+
return transition;
|
|
23281
|
+
};
|
|
23282
|
+
return {
|
|
23283
|
+
rebind,
|
|
23284
|
+
detach: async () => {
|
|
23285
|
+
await transitionTail;
|
|
23286
|
+
await detachCurrentCheckpoint();
|
|
23287
|
+
}
|
|
23288
|
+
};
|
|
23289
|
+
}
|
|
22687
23290
|
async function startWebUI(opts = {}) {
|
|
22688
23291
|
ensureSessionShell();
|
|
22689
23292
|
const ports = await resolvePorts(opts);
|
|
@@ -22751,6 +23354,14 @@ async function startWebUI(opts = {}) {
|
|
|
22751
23354
|
} = preContext;
|
|
22752
23355
|
let sessionStore = preContext.sessionStore;
|
|
22753
23356
|
let session = preContext.session;
|
|
23357
|
+
const todosCheckpoint = createStandaloneTodosCheckpointLifecycle({
|
|
23358
|
+
state: context.state,
|
|
23359
|
+
sessionsDir: wpaths.projectSessions,
|
|
23360
|
+
sessionId: session.id,
|
|
23361
|
+
events,
|
|
23362
|
+
traceId: context.traceId,
|
|
23363
|
+
warn: (message) => logger.warn(message)
|
|
23364
|
+
});
|
|
22754
23365
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
22755
23366
|
let modeId = preContext.modeId;
|
|
22756
23367
|
const needsSetup = preContext.needsSetup;
|
|
@@ -22877,7 +23488,7 @@ async function startWebUI(opts = {}) {
|
|
|
22877
23488
|
if (events.listenerCount("tool.confirm_needed") === 0) {
|
|
22878
23489
|
throw new Error("No permission confirmation surface is connected");
|
|
22879
23490
|
}
|
|
22880
|
-
const decision = await new Promise((
|
|
23491
|
+
const decision = await new Promise((resolve16) => {
|
|
22881
23492
|
events.emit("tool.confirm_needed", {
|
|
22882
23493
|
sessionId: context.session.id,
|
|
22883
23494
|
tool: confirmTool,
|
|
@@ -22887,7 +23498,7 @@ async function startWebUI(opts = {}) {
|
|
|
22887
23498
|
decisionSource: pending.decisionSource,
|
|
22888
23499
|
riskTier: pending.riskTier,
|
|
22889
23500
|
boundaryReason: pending.boundaryReason,
|
|
22890
|
-
resolve:
|
|
23501
|
+
resolve: resolve16
|
|
22891
23502
|
});
|
|
22892
23503
|
});
|
|
22893
23504
|
const rule = { tool: "language_package", pattern: pending.suggestedPattern };
|
|
@@ -22987,21 +23598,21 @@ async function startWebUI(opts = {}) {
|
|
|
22987
23598
|
});
|
|
22988
23599
|
}
|
|
22989
23600
|
async function touchProjectEntry(root, workDir) {
|
|
22990
|
-
const resolved =
|
|
23601
|
+
const resolved = path33.resolve(root);
|
|
22991
23602
|
const manifest = await loadManifest(globalConfigPath);
|
|
22992
23603
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22993
|
-
const existing = manifest.projects.find((p) =>
|
|
23604
|
+
const existing = manifest.projects.find((p) => path33.resolve(p.root) === resolved);
|
|
22994
23605
|
if (existing) {
|
|
22995
23606
|
existing.lastSeen = now;
|
|
22996
|
-
if (workDir) existing.lastWorkingDir =
|
|
23607
|
+
if (workDir) existing.lastWorkingDir = path33.resolve(workDir);
|
|
22997
23608
|
} else {
|
|
22998
23609
|
manifest.projects.push({
|
|
22999
|
-
name:
|
|
23610
|
+
name: path33.basename(resolved),
|
|
23000
23611
|
root: resolved,
|
|
23001
23612
|
slug: generateProjectSlug(resolved),
|
|
23002
23613
|
createdAt: now,
|
|
23003
23614
|
lastSeen: now,
|
|
23004
|
-
lastWorkingDir: workDir ?
|
|
23615
|
+
lastWorkingDir: workDir ? path33.resolve(workDir) : void 0
|
|
23005
23616
|
});
|
|
23006
23617
|
}
|
|
23007
23618
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -23102,6 +23713,7 @@ async function startWebUI(opts = {}) {
|
|
|
23102
23713
|
const cb = {
|
|
23103
23714
|
sessionStartPayload,
|
|
23104
23715
|
claimSession: (sessionId, target) => sessionIdentity.claim(sessionId, target),
|
|
23716
|
+
onBeforeSessionTodosReplaced: todosCheckpoint.rebind,
|
|
23105
23717
|
onSessionSwapped: async (sessionId, target) => {
|
|
23106
23718
|
await sessionIdentity.activate(sessionId, target);
|
|
23107
23719
|
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
@@ -23252,6 +23864,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23252
23864
|
...wssSecondary ? [wssSecondary] : []
|
|
23253
23865
|
],
|
|
23254
23866
|
onShutdown: async () => {
|
|
23867
|
+
await todosCheckpoint.detach();
|
|
23255
23868
|
await stopHeapWatchdog();
|
|
23256
23869
|
credentialWatcherClose?.();
|
|
23257
23870
|
brainMonitor.stop();
|
|
@@ -23277,7 +23890,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23277
23890
|
await memoryStore.dispose().catch(
|
|
23278
23891
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage14(err)}`)
|
|
23279
23892
|
);
|
|
23280
|
-
await unregisterInstance(process.pid,
|
|
23893
|
+
await unregisterInstance(process.pid, path33.dirname(globalConfigPath));
|
|
23281
23894
|
}
|
|
23282
23895
|
});
|
|
23283
23896
|
}
|