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