@wrongstack/webui-server 0.296.3 → 0.297.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +948 -429
- package/dist/index.js.map +4 -4
- package/dist/protocol/client-conversation.d.ts +1 -1
- package/dist/protocol/client-conversation.d.ts.map +1 -1
- package/dist/protocol/index.js +7 -1
- package/dist/protocol/index.js.map +2 -2
- package/dist/protocol/projections.d.ts +5 -1
- package/dist/protocol/projections.d.ts.map +1 -1
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/registry.d.ts.map +1 -1
- package/dist/protocol/server-conversation.d.ts +1 -1
- package/dist/protocol/server-conversation.d.ts.map +1 -1
- package/dist/server/backend-services.d.ts +6 -2
- package/dist/server/backend-services.d.ts.map +1 -1
- package/dist/server/collaboration-ws-handler.d.ts +2 -2
- package/dist/server/collaboration-ws-handler.d.ts.map +1 -1
- package/dist/server/connections-health-route.d.ts.map +1 -1
- package/dist/server/embedded-lifecycle.d.ts +2 -0
- package/dist/server/embedded-lifecycle.d.ts.map +1 -1
- package/dist/server/entry.js +870 -395
- package/dist/server/entry.js.map +4 -4
- package/dist/server/goal-ws-handler.d.ts +3 -1
- package/dist/server/goal-ws-handler.d.ts.map +1 -1
- package/dist/server/handlers.js.map +2 -2
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/kanban-supervisor.d.ts +6 -0
- package/dist/server/kanban-supervisor.d.ts.map +1 -1
- package/dist/server/mailbox-handlers.d.ts +2 -1
- package/dist/server/mailbox-handlers.d.ts.map +1 -1
- package/dist/server/mailbox-routes.d.ts +1 -0
- package/dist/server/mailbox-routes.d.ts.map +1 -1
- package/dist/server/memory-diagnostics.d.ts +69 -0
- package/dist/server/memory-diagnostics.d.ts.map +1 -0
- package/dist/server/sdd-board-ws-handler.d.ts +14 -8
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -1
- package/dist/server/sdd-wizard-wiring.d.ts +5 -6
- package/dist/server/sdd-wizard-wiring.d.ts.map +1 -1
- package/dist/server/sdd-wizard-ws-handler.d.ts +16 -6
- package/dist/server/sdd-wizard-ws-handler.d.ts.map +1 -1
- package/dist/server/start-webui.d.ts.map +1 -1
- package/dist/server/types.d.ts +2 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/worktree-ws-handler.d.ts +30 -2
- package/dist/server/worktree-ws-handler.d.ts.map +1 -1
- package/dist/server/ws-payload-validation.d.ts +8 -0
- package/dist/server/ws-payload-validation.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/server/entry.js
CHANGED
|
@@ -135,85 +135,85 @@ var ENUM_PREF_KEYS = {
|
|
|
135
135
|
autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
|
|
136
136
|
fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
|
|
137
137
|
};
|
|
138
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
138
|
+
function validateModelRuntimeValue(modelRuntime, path30) {
|
|
139
139
|
const reasoning = modelRuntime["reasoning"];
|
|
140
140
|
if (reasoning !== void 0) {
|
|
141
|
-
if (!isRecord(reasoning)) return `${
|
|
141
|
+
if (!isRecord(reasoning)) return `${path30}.reasoning must be an object when provided`;
|
|
142
142
|
const mode = reasoning["mode"];
|
|
143
143
|
const effort = reasoning["effort"];
|
|
144
144
|
const preserve = reasoning["preserve"];
|
|
145
145
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
146
|
-
return `${
|
|
146
|
+
return `${path30}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
147
147
|
}
|
|
148
148
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
149
|
-
return `${
|
|
149
|
+
return `${path30}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
150
150
|
}
|
|
151
151
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
152
|
-
return `${
|
|
152
|
+
return `${path30}.reasoning.preserve must be a boolean when provided`;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
const cache2 = modelRuntime["cache"];
|
|
156
156
|
if (cache2 !== void 0) {
|
|
157
|
-
if (!isRecord(cache2)) return `${
|
|
157
|
+
if (!isRecord(cache2)) return `${path30}.cache must be an object when provided`;
|
|
158
158
|
const ttl = cache2["ttl"];
|
|
159
159
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
160
|
-
return `${
|
|
160
|
+
return `${path30}.cache.ttl must be one of: 5m, 1h`;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
const parameters = modelRuntime["parameters"];
|
|
164
164
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
165
|
-
return `${
|
|
165
|
+
return `${path30}.parameters must be an object when provided`;
|
|
166
166
|
}
|
|
167
167
|
return null;
|
|
168
168
|
}
|
|
169
|
-
function validateModelBlackoutRule(rule,
|
|
169
|
+
function validateModelBlackoutRule(rule, path30) {
|
|
170
170
|
const id = rule["id"];
|
|
171
171
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
172
|
-
return `${
|
|
172
|
+
return `${path30}.id must be a non-empty string`;
|
|
173
173
|
}
|
|
174
174
|
const start = rule["start"];
|
|
175
175
|
if (typeof start !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(start)) {
|
|
176
|
-
return `${
|
|
176
|
+
return `${path30}.start must be a string in HH:mm (00:00-23:59) format`;
|
|
177
177
|
}
|
|
178
178
|
const end = rule["end"];
|
|
179
179
|
if (typeof end !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(end)) {
|
|
180
|
-
return `${
|
|
180
|
+
return `${path30}.end must be a string in HH:mm (00:00-23:59) format`;
|
|
181
181
|
}
|
|
182
182
|
if (rule["enabled"] !== void 0 && typeof rule["enabled"] !== "boolean") {
|
|
183
|
-
return `${
|
|
183
|
+
return `${path30}.enabled must be a boolean when provided`;
|
|
184
184
|
}
|
|
185
185
|
if (rule["provider"] !== void 0 && typeof rule["provider"] !== "string") {
|
|
186
|
-
return `${
|
|
186
|
+
return `${path30}.provider must be a string when provided`;
|
|
187
187
|
}
|
|
188
188
|
if (rule["model"] !== void 0 && typeof rule["model"] !== "string") {
|
|
189
|
-
return `${
|
|
189
|
+
return `${path30}.model must be a string when provided`;
|
|
190
190
|
}
|
|
191
191
|
if (rule["days"] !== void 0) {
|
|
192
|
-
if (!Array.isArray(rule["days"])) return `${
|
|
192
|
+
if (!Array.isArray(rule["days"])) return `${path30}.days must be an array when provided`;
|
|
193
193
|
const seen = /* @__PURE__ */ new Set();
|
|
194
194
|
for (const d of rule["days"]) {
|
|
195
195
|
if (typeof d !== "number" || !Number.isInteger(d) || d < 0 || d > 6) {
|
|
196
|
-
return `${
|
|
196
|
+
return `${path30}.days elements must be integers 0-6 when provided`;
|
|
197
197
|
}
|
|
198
|
-
if (seen.has(d)) return `${
|
|
198
|
+
if (seen.has(d)) return `${path30}.days contains duplicate day: ${d}`;
|
|
199
199
|
seen.add(d);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
if (rule["timezone"] !== void 0) {
|
|
203
203
|
if (typeof rule["timezone"] !== "string") {
|
|
204
|
-
return `${
|
|
204
|
+
return `${path30}.timezone must be a string when provided`;
|
|
205
205
|
}
|
|
206
206
|
try {
|
|
207
207
|
Intl.DateTimeFormat(void 0, { timeZone: rule["timezone"] });
|
|
208
208
|
} catch {
|
|
209
|
-
return `${
|
|
209
|
+
return `${path30}.timezone is not a valid IANA timezone (e.g. "America/New_York")`;
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
if (rule["label"] !== void 0 && typeof rule["label"] !== "string") {
|
|
213
|
-
return `${
|
|
213
|
+
return `${path30}.label must be a string when provided`;
|
|
214
214
|
}
|
|
215
215
|
if (rule["mode"] !== void 0 && rule["mode"] !== "blackout" && rule["mode"] !== "allow_only") {
|
|
216
|
-
return `${
|
|
216
|
+
return `${path30}.mode must be 'blackout' or 'allow_only' when provided`;
|
|
217
217
|
}
|
|
218
218
|
return null;
|
|
219
219
|
}
|
|
@@ -386,6 +386,37 @@ function validateMailboxMessagesPayload(payload) {
|
|
|
386
386
|
}
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
|
+
var MAILBOX_ACTIONS = /* @__PURE__ */ new Set(["mark-read", "acknowledge", "reopen", "soft-delete"]);
|
|
390
|
+
function validateMailboxActionPayload(payload) {
|
|
391
|
+
if (!isRecord2(payload)) {
|
|
392
|
+
return { ok: false, message: "mailbox.action payload must be an object" };
|
|
393
|
+
}
|
|
394
|
+
const requestId = payload["requestId"];
|
|
395
|
+
const mailId = payload["mailId"];
|
|
396
|
+
const action = payload["action"];
|
|
397
|
+
const readerId = payload["readerId"];
|
|
398
|
+
if (typeof requestId !== "string" || requestId.trim().length === 0) {
|
|
399
|
+
return { ok: false, message: "mailbox.action payload.requestId must be a non-empty string" };
|
|
400
|
+
}
|
|
401
|
+
if (typeof mailId !== "string" || mailId.trim().length === 0) {
|
|
402
|
+
return { ok: false, message: "mailbox.action payload.mailId must be a non-empty string" };
|
|
403
|
+
}
|
|
404
|
+
if (typeof action !== "string" || !MAILBOX_ACTIONS.has(action)) {
|
|
405
|
+
return { ok: false, message: "mailbox.action payload.action must be a supported action" };
|
|
406
|
+
}
|
|
407
|
+
if (typeof readerId !== "string" || readerId.trim().length === 0) {
|
|
408
|
+
return { ok: false, message: "mailbox.action payload.readerId must be a non-empty string" };
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
value: {
|
|
413
|
+
requestId: requestId.trim(),
|
|
414
|
+
mailId: mailId.trim(),
|
|
415
|
+
action,
|
|
416
|
+
readerId: readerId.trim()
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
389
420
|
var MAILBOX_SEND_TYPES = /* @__PURE__ */ new Set([
|
|
390
421
|
"note",
|
|
391
422
|
"ask",
|
|
@@ -402,6 +433,7 @@ function validateMailboxSendPayload(payload) {
|
|
|
402
433
|
return { ok: false, message: "mailbox.send payload must be an object" };
|
|
403
434
|
}
|
|
404
435
|
const requestId = payload["requestId"];
|
|
436
|
+
const rawFrom = payload["from"];
|
|
405
437
|
const rawTo = payload["to"];
|
|
406
438
|
const rawType = payload["type"];
|
|
407
439
|
const rawAudience = payload["audience"];
|
|
@@ -412,6 +444,9 @@ function validateMailboxSendPayload(payload) {
|
|
|
412
444
|
if (typeof requestId !== "string" || requestId.trim().length === 0) {
|
|
413
445
|
return { ok: false, message: "mailbox.send payload.requestId must be a non-empty string" };
|
|
414
446
|
}
|
|
447
|
+
if (rawFrom !== void 0 && (typeof rawFrom !== "string" || rawFrom.trim().length === 0)) {
|
|
448
|
+
return { ok: false, message: "mailbox.send payload.from must be a non-empty string when provided" };
|
|
449
|
+
}
|
|
415
450
|
if (typeof rawTo !== "string" || rawTo.trim().length === 0) {
|
|
416
451
|
return { ok: false, message: "mailbox.send payload.to must be a non-empty string" };
|
|
417
452
|
}
|
|
@@ -448,6 +483,7 @@ function validateMailboxSendPayload(payload) {
|
|
|
448
483
|
ok: true,
|
|
449
484
|
value: {
|
|
450
485
|
requestId: requestId.trim(),
|
|
486
|
+
...rawFrom !== void 0 ? { from: rawFrom.trim() } : {},
|
|
451
487
|
to,
|
|
452
488
|
type,
|
|
453
489
|
audience: rawAudience,
|
|
@@ -737,8 +773,8 @@ function validateShellOpenPayload(payload) {
|
|
|
737
773
|
if (!isRecord2(payload)) {
|
|
738
774
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
739
775
|
}
|
|
740
|
-
const
|
|
741
|
-
if (typeof
|
|
776
|
+
const path30 = payload["path"];
|
|
777
|
+
if (typeof path30 !== "string" || path30.trim().length === 0) {
|
|
742
778
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
743
779
|
}
|
|
744
780
|
const target = payload["target"];
|
|
@@ -751,7 +787,7 @@ function validateShellOpenPayload(payload) {
|
|
|
751
787
|
return {
|
|
752
788
|
ok: true,
|
|
753
789
|
value: {
|
|
754
|
-
path:
|
|
790
|
+
path: path30,
|
|
755
791
|
...target !== void 0 ? { target } : {}
|
|
756
792
|
}
|
|
757
793
|
};
|
|
@@ -760,14 +796,14 @@ function validateGitDiffPayload(payload) {
|
|
|
760
796
|
if (!isRecord2(payload)) {
|
|
761
797
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
762
798
|
}
|
|
763
|
-
const
|
|
764
|
-
if (
|
|
799
|
+
const path30 = payload["path"];
|
|
800
|
+
if (path30 === void 0 || path30 === null) {
|
|
765
801
|
return { ok: true, value: { path: "" } };
|
|
766
802
|
}
|
|
767
|
-
if (typeof
|
|
803
|
+
if (typeof path30 !== "string") {
|
|
768
804
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
769
805
|
}
|
|
770
|
-
return { ok: true, value: { path:
|
|
806
|
+
return { ok: true, value: { path: path30 } };
|
|
771
807
|
}
|
|
772
808
|
function validateProjectsAddPayload(payload) {
|
|
773
809
|
if (!isRecord2(payload)) {
|
|
@@ -1529,6 +1565,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1529
1565
|
for (const off of this.offs) off();
|
|
1530
1566
|
this.offs.length = 0;
|
|
1531
1567
|
this.stopBroadcast();
|
|
1568
|
+
this.clients.clear();
|
|
1532
1569
|
}
|
|
1533
1570
|
// ── Inbound client messages ────────────────────────────────────────────
|
|
1534
1571
|
/**
|
|
@@ -1588,9 +1625,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1588
1625
|
if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
|
|
1589
1626
|
this.send(
|
|
1590
1627
|
ws,
|
|
1591
|
-
this.errorMessage(
|
|
1592
|
-
`collab.join sessionId mismatch (active: ${activeSessionId})`
|
|
1593
|
-
)
|
|
1628
|
+
this.errorMessage(`collab.join sessionId mismatch (active: ${activeSessionId})`)
|
|
1594
1629
|
);
|
|
1595
1630
|
return;
|
|
1596
1631
|
}
|
|
@@ -1601,26 +1636,19 @@ var CollaborationWebSocketHandler = class {
|
|
|
1601
1636
|
if (role === "controller" && !this.bus) {
|
|
1602
1637
|
this.send(
|
|
1603
1638
|
ws,
|
|
1604
|
-
this.errorMessage(
|
|
1605
|
-
`role 'controller' is not available: server has no CollaborationBus`
|
|
1606
|
-
)
|
|
1639
|
+
this.errorMessage(`role 'controller' is not available: server has no CollaborationBus`)
|
|
1607
1640
|
);
|
|
1608
1641
|
return;
|
|
1609
1642
|
}
|
|
1610
1643
|
if (role === "annotator" && !this.annotations) {
|
|
1611
1644
|
this.send(
|
|
1612
1645
|
ws,
|
|
1613
|
-
this.errorMessage(
|
|
1614
|
-
`role 'annotator' is not available: server has no annotations store`
|
|
1615
|
-
)
|
|
1646
|
+
this.errorMessage(`role 'annotator' is not available: server has no annotations store`)
|
|
1616
1647
|
);
|
|
1617
1648
|
return;
|
|
1618
1649
|
}
|
|
1619
1650
|
if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
|
|
1620
|
-
this.send(
|
|
1621
|
-
ws,
|
|
1622
|
-
this.errorMessage(`role '${role}' requires explicit server authorization`)
|
|
1623
|
-
);
|
|
1651
|
+
this.send(ws, this.errorMessage(`role '${role}' requires explicit server authorization`));
|
|
1624
1652
|
return;
|
|
1625
1653
|
}
|
|
1626
1654
|
const participant = {
|
|
@@ -1649,14 +1677,10 @@ var CollaborationWebSocketHandler = class {
|
|
|
1649
1677
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
1650
1678
|
if (this.reader) {
|
|
1651
1679
|
this.replayHistory(ws, sessionId).catch((err) => {
|
|
1652
|
-
this.logger.debug?.(
|
|
1653
|
-
`collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`
|
|
1654
|
-
);
|
|
1680
|
+
this.logger.debug?.(`collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`);
|
|
1655
1681
|
});
|
|
1656
1682
|
}
|
|
1657
|
-
this.logger.debug?.(
|
|
1658
|
-
`collab: participant ${participant.participantId} joined ${sessionId}`
|
|
1659
|
-
);
|
|
1683
|
+
this.logger.debug?.(`collab: participant ${participant.participantId} joined ${sessionId}`);
|
|
1660
1684
|
}
|
|
1661
1685
|
leave(ws) {
|
|
1662
1686
|
this.handleDisconnect(ws);
|
|
@@ -1727,18 +1751,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
1727
1751
|
}
|
|
1728
1752
|
const payload = raw;
|
|
1729
1753
|
if (!payload?.sessionId || typeof payload.atEventIndex !== "number" || typeof payload.text !== "string") {
|
|
1730
|
-
this.send(
|
|
1731
|
-
ws,
|
|
1732
|
-
this.errorMessage("annotate requires { sessionId, atEventIndex, text }")
|
|
1733
|
-
);
|
|
1754
|
+
this.send(ws, this.errorMessage("annotate requires { sessionId, atEventIndex, text }"));
|
|
1734
1755
|
return;
|
|
1735
1756
|
}
|
|
1736
1757
|
if (payload.sessionId !== participant.sessionId) {
|
|
1737
1758
|
this.send(
|
|
1738
1759
|
ws,
|
|
1739
|
-
this.errorMessage(
|
|
1740
|
-
`annotate sessionId mismatch (joined: ${participant.sessionId})`
|
|
1741
|
-
)
|
|
1760
|
+
this.errorMessage(`annotate sessionId mismatch (joined: ${participant.sessionId})`)
|
|
1742
1761
|
);
|
|
1743
1762
|
return;
|
|
1744
1763
|
}
|
|
@@ -1765,12 +1784,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1765
1784
|
}
|
|
1766
1785
|
});
|
|
1767
1786
|
} catch (err) {
|
|
1768
|
-
this.send(
|
|
1769
|
-
ws,
|
|
1770
|
-
this.errorMessage(
|
|
1771
|
-
`annotation rejected: ${toErrorMessage(err)}`
|
|
1772
|
-
)
|
|
1773
|
-
);
|
|
1787
|
+
this.send(ws, this.errorMessage(`annotation rejected: ${toErrorMessage(err)}`));
|
|
1774
1788
|
}
|
|
1775
1789
|
}
|
|
1776
1790
|
async handleResolve(ws, raw) {
|
|
@@ -1786,26 +1800,19 @@ var CollaborationWebSocketHandler = class {
|
|
|
1786
1800
|
if (participant.role !== "annotator") {
|
|
1787
1801
|
this.send(
|
|
1788
1802
|
ws,
|
|
1789
|
-
this.errorMessage(
|
|
1790
|
-
`resolve requires the 'annotator' role (current: '${participant.role}')`
|
|
1791
|
-
)
|
|
1803
|
+
this.errorMessage(`resolve requires the 'annotator' role (current: '${participant.role}')`)
|
|
1792
1804
|
);
|
|
1793
1805
|
return;
|
|
1794
1806
|
}
|
|
1795
1807
|
const payload = raw;
|
|
1796
1808
|
if (!payload?.sessionId || !payload.annotationId) {
|
|
1797
|
-
this.send(
|
|
1798
|
-
ws,
|
|
1799
|
-
this.errorMessage("resolve requires { sessionId, annotationId }")
|
|
1800
|
-
);
|
|
1809
|
+
this.send(ws, this.errorMessage("resolve requires { sessionId, annotationId }"));
|
|
1801
1810
|
return;
|
|
1802
1811
|
}
|
|
1803
1812
|
if (payload.sessionId !== participant.sessionId) {
|
|
1804
1813
|
this.send(
|
|
1805
1814
|
ws,
|
|
1806
|
-
this.errorMessage(
|
|
1807
|
-
`resolve sessionId mismatch (joined: ${participant.sessionId})`
|
|
1808
|
-
)
|
|
1815
|
+
this.errorMessage(`resolve sessionId mismatch (joined: ${participant.sessionId})`)
|
|
1809
1816
|
);
|
|
1810
1817
|
return;
|
|
1811
1818
|
}
|
|
@@ -1816,10 +1823,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1816
1823
|
resolvedBy: participant.participantId
|
|
1817
1824
|
});
|
|
1818
1825
|
if (!updated) {
|
|
1819
|
-
this.send(
|
|
1820
|
-
ws,
|
|
1821
|
-
this.errorMessage(`annotation not found: ${payload.annotationId}`)
|
|
1822
|
-
);
|
|
1826
|
+
this.send(ws, this.errorMessage(`annotation not found: ${payload.annotationId}`));
|
|
1823
1827
|
return;
|
|
1824
1828
|
}
|
|
1825
1829
|
this.broadcast(payload.sessionId, {
|
|
@@ -1832,12 +1836,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1832
1836
|
}
|
|
1833
1837
|
});
|
|
1834
1838
|
} catch (err) {
|
|
1835
|
-
this.send(
|
|
1836
|
-
ws,
|
|
1837
|
-
this.errorMessage(
|
|
1838
|
-
`resolve failed: ${toErrorMessage(err)}`
|
|
1839
|
-
)
|
|
1840
|
-
);
|
|
1839
|
+
this.send(ws, this.errorMessage(`resolve failed: ${toErrorMessage(err)}`));
|
|
1841
1840
|
}
|
|
1842
1841
|
}
|
|
1843
1842
|
// ── Event subscription (live mirror) ───────────────────────────────────
|
|
@@ -1908,9 +1907,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1908
1907
|
seen++;
|
|
1909
1908
|
}
|
|
1910
1909
|
} catch (err) {
|
|
1911
|
-
this.logger.debug?.(
|
|
1912
|
-
`collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`
|
|
1913
|
-
);
|
|
1910
|
+
this.logger.debug?.(`collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`);
|
|
1914
1911
|
return;
|
|
1915
1912
|
}
|
|
1916
1913
|
const tail2 = seen <= REPLAY_LIMIT ? ring.slice(0, seen) : (
|
|
@@ -1992,9 +1989,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1992
1989
|
try {
|
|
1993
1990
|
sendSerialized(p.ws, data);
|
|
1994
1991
|
} catch (err) {
|
|
1995
|
-
this.logger.debug?.(
|
|
1996
|
-
`collab broadcast failed: ${toErrorMessage(err)}`
|
|
1997
|
-
);
|
|
1992
|
+
this.logger.debug?.(`collab broadcast failed: ${toErrorMessage(err)}`);
|
|
1998
1993
|
}
|
|
1999
1994
|
}
|
|
2000
1995
|
}
|
|
@@ -2021,9 +2016,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2021
2016
|
if (participant.role !== "controller") {
|
|
2022
2017
|
this.send(
|
|
2023
2018
|
ws,
|
|
2024
|
-
this.errorMessage(
|
|
2025
|
-
`pause requires the 'controller' role (current: '${participant.role}')`
|
|
2026
|
-
)
|
|
2019
|
+
this.errorMessage(`pause requires the 'controller' role (current: '${participant.role}')`)
|
|
2027
2020
|
);
|
|
2028
2021
|
return;
|
|
2029
2022
|
}
|
|
@@ -2068,9 +2061,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2068
2061
|
if (participant.role !== "controller") {
|
|
2069
2062
|
this.send(
|
|
2070
2063
|
ws,
|
|
2071
|
-
this.errorMessage(
|
|
2072
|
-
`resume requires the 'controller' role (current: '${participant.role}')`
|
|
2073
|
-
)
|
|
2064
|
+
this.errorMessage(`resume requires the 'controller' role (current: '${participant.role}')`)
|
|
2074
2065
|
);
|
|
2075
2066
|
return;
|
|
2076
2067
|
}
|
|
@@ -2168,9 +2159,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2168
2159
|
if (payload.sessionId !== participant.sessionId) {
|
|
2169
2160
|
this.send(
|
|
2170
2161
|
ws,
|
|
2171
|
-
this.errorMessage(
|
|
2172
|
-
`inject_tool sessionId mismatch (joined: ${participant.sessionId})`
|
|
2173
|
-
)
|
|
2162
|
+
this.errorMessage(`inject_tool sessionId mismatch (joined: ${participant.sessionId})`)
|
|
2174
2163
|
);
|
|
2175
2164
|
return;
|
|
2176
2165
|
}
|
|
@@ -2184,9 +2173,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2184
2173
|
if (!queued) {
|
|
2185
2174
|
this.send(
|
|
2186
2175
|
ws,
|
|
2187
|
-
this.errorMessage(
|
|
2188
|
-
`an injection for toolUseId ${payload.toolUseId} is already queued`
|
|
2189
|
-
)
|
|
2176
|
+
this.errorMessage(`an injection for toolUseId ${payload.toolUseId} is already queued`)
|
|
2190
2177
|
);
|
|
2191
2178
|
return;
|
|
2192
2179
|
}
|
|
@@ -4288,8 +4275,8 @@ function jsonByteLength(value) {
|
|
|
4288
4275
|
return MAX_PAYLOAD_BYTES + 1;
|
|
4289
4276
|
}
|
|
4290
4277
|
}
|
|
4291
|
-
function error(errors,
|
|
4292
|
-
errors.push({ path:
|
|
4278
|
+
function error(errors, path30, code, message) {
|
|
4279
|
+
errors.push({ path: path30, code, message });
|
|
4293
4280
|
}
|
|
4294
4281
|
function isMessageRole(value) {
|
|
4295
4282
|
return value === "user" || value === "assistant" || value === "system";
|
|
@@ -4297,25 +4284,25 @@ function isMessageRole(value) {
|
|
|
4297
4284
|
function isPlainJsonObject(value) {
|
|
4298
4285
|
return isRecord3(value);
|
|
4299
4286
|
}
|
|
4300
|
-
function validateCacheControl(value,
|
|
4287
|
+
function validateCacheControl(value, path30, errors) {
|
|
4301
4288
|
if (value === void 0) return void 0;
|
|
4302
4289
|
if (!isRecord3(value) || value["type"] !== "ephemeral") {
|
|
4303
|
-
error(errors,
|
|
4290
|
+
error(errors, path30, "INVALID_CACHE_CONTROL", 'cache_control must be { type: "ephemeral" }.');
|
|
4304
4291
|
return void 0;
|
|
4305
4292
|
}
|
|
4306
4293
|
return { type: "ephemeral" };
|
|
4307
4294
|
}
|
|
4308
|
-
function validateProviderMeta(value,
|
|
4295
|
+
function validateProviderMeta(value, path30, errors) {
|
|
4309
4296
|
if (value === void 0) return void 0;
|
|
4310
4297
|
if (!isPlainJsonObject(value)) {
|
|
4311
|
-
error(errors,
|
|
4298
|
+
error(errors, path30, "INVALID_PROVIDER_META", "providerMeta must be a JSON object.");
|
|
4312
4299
|
return void 0;
|
|
4313
4300
|
}
|
|
4314
4301
|
return value;
|
|
4315
4302
|
}
|
|
4316
|
-
function validateBlock(value,
|
|
4303
|
+
function validateBlock(value, path30, errors) {
|
|
4317
4304
|
if (!isRecord3(value)) {
|
|
4318
|
-
error(errors,
|
|
4305
|
+
error(errors, path30, "INVALID_BLOCK", "Content block must be an object.");
|
|
4319
4306
|
return void 0;
|
|
4320
4307
|
}
|
|
4321
4308
|
const type = value["type"];
|
|
@@ -4323,14 +4310,14 @@ function validateBlock(value, path29, errors) {
|
|
|
4323
4310
|
case "text": {
|
|
4324
4311
|
const text = value["text"];
|
|
4325
4312
|
if (typeof text !== "string") {
|
|
4326
|
-
error(errors, `${
|
|
4313
|
+
error(errors, `${path30}/text`, "INVALID_TEXT", "Text block text must be a string.");
|
|
4327
4314
|
return void 0;
|
|
4328
4315
|
}
|
|
4329
4316
|
if (text.length > MAX_STRING_LENGTH) {
|
|
4330
|
-
error(errors, `${
|
|
4317
|
+
error(errors, `${path30}/text`, "TEXT_TOO_LARGE", "Text block is too large.");
|
|
4331
4318
|
return void 0;
|
|
4332
4319
|
}
|
|
4333
|
-
const cacheControl = validateCacheControl(value["cache_control"], `${
|
|
4320
|
+
const cacheControl = validateCacheControl(value["cache_control"], `${path30}/cache_control`, errors);
|
|
4334
4321
|
return cacheControl ? { type: "text", text, cache_control: cacheControl } : { type: "text", text };
|
|
4335
4322
|
}
|
|
4336
4323
|
case "tool_use": {
|
|
@@ -4338,15 +4325,15 @@ function validateBlock(value, path29, errors) {
|
|
|
4338
4325
|
const name2 = value["name"];
|
|
4339
4326
|
const input = value["input"];
|
|
4340
4327
|
if (typeof id !== "string" || id.length === 0) {
|
|
4341
|
-
error(errors, `${
|
|
4328
|
+
error(errors, `${path30}/id`, "INVALID_TOOL_USE_ID", "tool_use.id must be a non-empty string.");
|
|
4342
4329
|
}
|
|
4343
4330
|
if (typeof name2 !== "string" || name2.length === 0) {
|
|
4344
|
-
error(errors, `${
|
|
4331
|
+
error(errors, `${path30}/name`, "INVALID_TOOL_NAME", "tool_use.name must be a non-empty string.");
|
|
4345
4332
|
}
|
|
4346
4333
|
if (!isPlainJsonObject(input)) {
|
|
4347
|
-
error(errors, `${
|
|
4334
|
+
error(errors, `${path30}/input`, "INVALID_TOOL_INPUT", "tool_use.input must be an object.");
|
|
4348
4335
|
}
|
|
4349
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4336
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path30}/providerMeta`, errors);
|
|
4350
4337
|
if (typeof id !== "string" || id.length === 0 || typeof name2 !== "string" || name2.length === 0 || !isPlainJsonObject(input)) {
|
|
4351
4338
|
return void 0;
|
|
4352
4339
|
}
|
|
@@ -4358,18 +4345,18 @@ function validateBlock(value, path29, errors) {
|
|
|
4358
4345
|
const content = value["content"];
|
|
4359
4346
|
const isError = value["is_error"];
|
|
4360
4347
|
if (typeof toolUseId !== "string" || toolUseId.length === 0) {
|
|
4361
|
-
error(errors, `${
|
|
4348
|
+
error(errors, `${path30}/tool_use_id`, "INVALID_TOOL_RESULT_ID", "tool_result.tool_use_id must be a non-empty string.");
|
|
4362
4349
|
}
|
|
4363
4350
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
4364
|
-
error(errors, `${
|
|
4351
|
+
error(errors, `${path30}/name`, "INVALID_TOOL_RESULT_NAME", "tool_result.name must be a string.");
|
|
4365
4352
|
}
|
|
4366
4353
|
if (typeof content !== "string") {
|
|
4367
|
-
error(errors, `${
|
|
4354
|
+
error(errors, `${path30}/content`, "INVALID_TOOL_RESULT_CONTENT", "tool_result.content must be a string.");
|
|
4368
4355
|
} else if (content.length > MAX_STRING_LENGTH) {
|
|
4369
|
-
error(errors, `${
|
|
4356
|
+
error(errors, `${path30}/content`, "TOOL_RESULT_TOO_LARGE", "tool_result.content is too large.");
|
|
4370
4357
|
}
|
|
4371
4358
|
if (isError !== void 0 && typeof isError !== "boolean") {
|
|
4372
|
-
error(errors, `${
|
|
4359
|
+
error(errors, `${path30}/is_error`, "INVALID_TOOL_RESULT_ERROR", "tool_result.is_error must be boolean.");
|
|
4373
4360
|
}
|
|
4374
4361
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || typeof content !== "string") return void 0;
|
|
4375
4362
|
return {
|
|
@@ -4383,25 +4370,25 @@ function validateBlock(value, path29, errors) {
|
|
|
4383
4370
|
case "image": {
|
|
4384
4371
|
const source = value["source"];
|
|
4385
4372
|
if (!isRecord3(source)) {
|
|
4386
|
-
error(errors, `${
|
|
4373
|
+
error(errors, `${path30}/source`, "INVALID_IMAGE_SOURCE", "image.source must be an object.");
|
|
4387
4374
|
return void 0;
|
|
4388
4375
|
}
|
|
4389
4376
|
const sourceType = source["type"];
|
|
4390
4377
|
if (sourceType !== "base64" && sourceType !== "url") {
|
|
4391
|
-
error(errors, `${
|
|
4378
|
+
error(errors, `${path30}/source/type`, "INVALID_IMAGE_SOURCE_TYPE", "image.source.type must be base64 or url.");
|
|
4392
4379
|
return void 0;
|
|
4393
4380
|
}
|
|
4394
4381
|
const mediaType = source["media_type"];
|
|
4395
4382
|
const data = source["data"];
|
|
4396
4383
|
const url = source["url"];
|
|
4397
4384
|
if (mediaType !== void 0 && typeof mediaType !== "string") {
|
|
4398
|
-
error(errors, `${
|
|
4385
|
+
error(errors, `${path30}/source/media_type`, "INVALID_IMAGE_MEDIA_TYPE", "image.source.media_type must be a string.");
|
|
4399
4386
|
}
|
|
4400
4387
|
if (data !== void 0 && typeof data !== "string") {
|
|
4401
|
-
error(errors, `${
|
|
4388
|
+
error(errors, `${path30}/source/data`, "INVALID_IMAGE_DATA", "image.source.data must be a string.");
|
|
4402
4389
|
}
|
|
4403
4390
|
if (url !== void 0 && typeof url !== "string") {
|
|
4404
|
-
error(errors, `${
|
|
4391
|
+
error(errors, `${path30}/source/url`, "INVALID_IMAGE_URL", "image.source.url must be a string.");
|
|
4405
4392
|
}
|
|
4406
4393
|
return {
|
|
4407
4394
|
type: "image",
|
|
@@ -4417,13 +4404,13 @@ function validateBlock(value, path29, errors) {
|
|
|
4417
4404
|
const thinking = value["thinking"];
|
|
4418
4405
|
const signature = value["signature"];
|
|
4419
4406
|
if (typeof thinking !== "string") {
|
|
4420
|
-
error(errors, `${
|
|
4407
|
+
error(errors, `${path30}/thinking`, "INVALID_THINKING", "thinking.thinking must be a string.");
|
|
4421
4408
|
return void 0;
|
|
4422
4409
|
}
|
|
4423
4410
|
if (signature !== void 0 && typeof signature !== "string") {
|
|
4424
|
-
error(errors, `${
|
|
4411
|
+
error(errors, `${path30}/signature`, "INVALID_THINKING_SIGNATURE", "thinking.signature must be a string.");
|
|
4425
4412
|
}
|
|
4426
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4413
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path30}/providerMeta`, errors);
|
|
4427
4414
|
return {
|
|
4428
4415
|
type: "thinking",
|
|
4429
4416
|
thinking,
|
|
@@ -4432,7 +4419,7 @@ function validateBlock(value, path29, errors) {
|
|
|
4432
4419
|
};
|
|
4433
4420
|
}
|
|
4434
4421
|
default:
|
|
4435
|
-
error(errors, `${
|
|
4422
|
+
error(errors, `${path30}/type`, "UNKNOWN_BLOCK_TYPE", `Unknown content block type: ${String(type)}`);
|
|
4436
4423
|
return void 0;
|
|
4437
4424
|
}
|
|
4438
4425
|
}
|
|
@@ -4450,39 +4437,39 @@ function validateContextEditorMessages(value, currentMessageCount = 0) {
|
|
|
4450
4437
|
error(errors, "/messages", "PAYLOAD_TOO_LARGE", "Context editor payload is too large.");
|
|
4451
4438
|
}
|
|
4452
4439
|
value.forEach((item, index) => {
|
|
4453
|
-
const
|
|
4440
|
+
const path30 = `/messages/${index}`;
|
|
4454
4441
|
if (!isRecord3(item)) {
|
|
4455
|
-
error(errors,
|
|
4442
|
+
error(errors, path30, "INVALID_MESSAGE", "Message must be an object.");
|
|
4456
4443
|
return;
|
|
4457
4444
|
}
|
|
4458
4445
|
const role = item["role"];
|
|
4459
4446
|
if (!isMessageRole(role)) {
|
|
4460
|
-
error(errors, `${
|
|
4447
|
+
error(errors, `${path30}/role`, "INVALID_ROLE", "Message role must be user, assistant, or system.");
|
|
4461
4448
|
return;
|
|
4462
4449
|
}
|
|
4463
4450
|
const rawContent = item["content"];
|
|
4464
4451
|
let content;
|
|
4465
4452
|
if (typeof rawContent === "string") {
|
|
4466
4453
|
if (rawContent.length > MAX_STRING_LENGTH) {
|
|
4467
|
-
error(errors, `${
|
|
4454
|
+
error(errors, `${path30}/content`, "CONTENT_TOO_LARGE", "Message content is too large.");
|
|
4468
4455
|
return;
|
|
4469
4456
|
}
|
|
4470
4457
|
content = rawContent;
|
|
4471
4458
|
} else if (Array.isArray(rawContent)) {
|
|
4472
4459
|
const blocks = [];
|
|
4473
4460
|
rawContent.forEach((block, blockIndex) => {
|
|
4474
|
-
const parsed = validateBlock(block, `${
|
|
4461
|
+
const parsed = validateBlock(block, `${path30}/content/${blockIndex}`, errors);
|
|
4475
4462
|
if (parsed) blocks.push(parsed);
|
|
4476
4463
|
});
|
|
4477
4464
|
content = blocks;
|
|
4478
4465
|
} else {
|
|
4479
|
-
error(errors, `${
|
|
4466
|
+
error(errors, `${path30}/content`, "INVALID_CONTENT", "Message content must be a string or content block array.");
|
|
4480
4467
|
return;
|
|
4481
4468
|
}
|
|
4482
4469
|
const ts = item["ts"];
|
|
4483
4470
|
if (ts !== void 0) {
|
|
4484
4471
|
if (typeof ts !== "string" || Number.isNaN(Date.parse(ts))) {
|
|
4485
|
-
error(errors, `${
|
|
4472
|
+
error(errors, `${path30}/ts`, "INVALID_TIMESTAMP", "Message ts must be an ISO-like timestamp string.");
|
|
4486
4473
|
return;
|
|
4487
4474
|
}
|
|
4488
4475
|
}
|
|
@@ -4871,6 +4858,7 @@ import { isSageProjectServerAvailable, SageProjectServerConnection } from "@wron
|
|
|
4871
4858
|
import {
|
|
4872
4859
|
checkCodebaseIndexServerHealth,
|
|
4873
4860
|
getIndexState,
|
|
4861
|
+
resolveProjectIndexDaemonAvailability,
|
|
4874
4862
|
shutdownCodebaseIndexServer
|
|
4875
4863
|
} from "@wrongstack/tools";
|
|
4876
4864
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
@@ -4963,6 +4951,19 @@ async function chronicleHealth(projectRoot) {
|
|
|
4963
4951
|
}
|
|
4964
4952
|
async function codebaseIndexHealth(projectRoot, indexDir) {
|
|
4965
4953
|
const startedAt = Date.now();
|
|
4954
|
+
const availability = resolveProjectIndexDaemonAvailability(projectRoot, indexDir);
|
|
4955
|
+
if (availability.kind === "endpoint-invalid") {
|
|
4956
|
+
return {
|
|
4957
|
+
id: "codebase-index",
|
|
4958
|
+
label: "Codebase index",
|
|
4959
|
+
status: "unavailable",
|
|
4960
|
+
required: false,
|
|
4961
|
+
mode: "endpoint-invalid",
|
|
4962
|
+
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.`,
|
|
4963
|
+
endpoint: availability.endpoint,
|
|
4964
|
+
latencyMs: Date.now() - startedAt
|
|
4965
|
+
};
|
|
4966
|
+
}
|
|
4966
4967
|
try {
|
|
4967
4968
|
const health = await checkCodebaseIndexServerHealth(projectRoot, indexDir, {
|
|
4968
4969
|
timeoutMs: 2e3
|
|
@@ -5406,15 +5407,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5406
5407
|
if (!m) continue;
|
|
5407
5408
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5408
5409
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5409
|
-
let
|
|
5410
|
-
if (
|
|
5410
|
+
let path30 = m[3] ?? "";
|
|
5411
|
+
if (path30 === "") {
|
|
5411
5412
|
i += 1;
|
|
5412
|
-
|
|
5413
|
+
path30 = parts[i + 1] ?? parts[i] ?? "";
|
|
5413
5414
|
i += 1;
|
|
5414
5415
|
}
|
|
5415
|
-
if (!
|
|
5416
|
-
const prev = counts.get(
|
|
5417
|
-
counts.set(
|
|
5416
|
+
if (!path30) continue;
|
|
5417
|
+
const prev = counts.get(path30) ?? { added: 0, deleted: 0 };
|
|
5418
|
+
counts.set(path30, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5418
5419
|
}
|
|
5419
5420
|
};
|
|
5420
5421
|
parseNumstat(unstagedNumstat);
|
|
@@ -5426,7 +5427,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5426
5427
|
if (!rec || rec.length < 3) continue;
|
|
5427
5428
|
const x = rec[0] ?? " ";
|
|
5428
5429
|
const y = rec[1] ?? " ";
|
|
5429
|
-
const
|
|
5430
|
+
const path30 = rec.slice(3);
|
|
5430
5431
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5431
5432
|
if (isRename) i += 1;
|
|
5432
5433
|
let status;
|
|
@@ -5438,13 +5439,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5438
5439
|
else if (x === "D" || y === "D") status = "D";
|
|
5439
5440
|
else status = "M";
|
|
5440
5441
|
const staged = x !== " " && x !== "?";
|
|
5441
|
-
let added = counts.get(
|
|
5442
|
-
let deleted = counts.get(
|
|
5442
|
+
let added = counts.get(path30)?.added ?? 0;
|
|
5443
|
+
let deleted = counts.get(path30)?.deleted ?? 0;
|
|
5443
5444
|
if (status === "?") {
|
|
5444
5445
|
added = 0;
|
|
5445
5446
|
deleted = 0;
|
|
5446
5447
|
}
|
|
5447
|
-
files.push({ path:
|
|
5448
|
+
files.push({ path: path30, status, added, deleted, staged });
|
|
5448
5449
|
}
|
|
5449
5450
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5450
5451
|
} catch (err) {
|
|
@@ -5455,21 +5456,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5455
5456
|
}
|
|
5456
5457
|
}
|
|
5457
5458
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5458
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5459
|
+
async function handleGitDiff(ws, projectRoot, path30) {
|
|
5459
5460
|
const cwd = projectRoot || void 0;
|
|
5460
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5461
|
-
if (!
|
|
5461
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path30, ...extra } });
|
|
5462
|
+
if (!path30 || path30.includes("\0") || path30.includes("..") || nodePath.isAbsolute(path30)) {
|
|
5462
5463
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5463
5464
|
return;
|
|
5464
5465
|
}
|
|
5465
5466
|
try {
|
|
5466
5467
|
const git = makeGit(cwd);
|
|
5467
5468
|
const { readFile: readFile11 } = await import("node:fs/promises");
|
|
5468
|
-
const { join:
|
|
5469
|
-
const oldText = await git(["show", `HEAD:${
|
|
5469
|
+
const { join: join16 } = await import("node:path");
|
|
5470
|
+
const oldText = await git(["show", `HEAD:${path30}`]);
|
|
5470
5471
|
let newText = "";
|
|
5471
5472
|
try {
|
|
5472
|
-
const abs = cwd ?
|
|
5473
|
+
const abs = cwd ? join16(cwd, path30) : path30;
|
|
5473
5474
|
const buf = await readFile11(abs);
|
|
5474
5475
|
if (buf.includes(0)) {
|
|
5475
5476
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5531,10 +5532,7 @@ async function handleGoalSnapshotRoute(ws, msg, handlers) {
|
|
|
5531
5532
|
}
|
|
5532
5533
|
|
|
5533
5534
|
// src/server/goal-ws-handler.ts
|
|
5534
|
-
import {
|
|
5535
|
-
import {
|
|
5536
|
-
assignNickname
|
|
5537
|
-
} from "@wrongstack/core/coordination";
|
|
5535
|
+
import { assignNickname } from "@wrongstack/core/coordination";
|
|
5538
5536
|
import {
|
|
5539
5537
|
GoalAssessor,
|
|
5540
5538
|
GoalPlanner,
|
|
@@ -5542,6 +5540,7 @@ import {
|
|
|
5542
5540
|
PhaseOrchestrator,
|
|
5543
5541
|
PhaseStore
|
|
5544
5542
|
} from "@wrongstack/core/goal";
|
|
5543
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
5545
5544
|
import { WorktreeManager } from "@wrongstack/core/worktree";
|
|
5546
5545
|
|
|
5547
5546
|
// src/server/git-process.ts
|
|
@@ -5578,12 +5577,7 @@ function deriveTitle(goal) {
|
|
|
5578
5577
|
return trimmed || "Goal";
|
|
5579
5578
|
}
|
|
5580
5579
|
async function commitsSince(cwd, baseSha, branch) {
|
|
5581
|
-
const output = await gitStdout(cwd, [
|
|
5582
|
-
"log",
|
|
5583
|
-
"--reverse",
|
|
5584
|
-
"--format=%H",
|
|
5585
|
-
`${baseSha}..${branch}`
|
|
5586
|
-
]);
|
|
5580
|
+
const output = await gitStdout(cwd, ["log", "--reverse", "--format=%H", `${baseSha}..${branch}`]);
|
|
5587
5581
|
if (output === null) return [];
|
|
5588
5582
|
return output.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
5589
5583
|
}
|
|
@@ -5634,6 +5628,17 @@ var GoalWebSocketHandler = class {
|
|
|
5634
5628
|
ws.on("error", () => this.clients.delete(client));
|
|
5635
5629
|
this.sendState(client);
|
|
5636
5630
|
}
|
|
5631
|
+
/** Release timers, in-flight work, and socket references owned by this host. */
|
|
5632
|
+
dispose() {
|
|
5633
|
+
this.stopping = true;
|
|
5634
|
+
this.abort?.abort();
|
|
5635
|
+
this.abort = null;
|
|
5636
|
+
this.assessAbort?.abort();
|
|
5637
|
+
this.assessAbort = null;
|
|
5638
|
+
this.orchestrator?.stop();
|
|
5639
|
+
this.stopBroadcast();
|
|
5640
|
+
this.clients.clear();
|
|
5641
|
+
}
|
|
5637
5642
|
async handleMessage(ws, msg) {
|
|
5638
5643
|
switch (msg.type) {
|
|
5639
5644
|
case "goal.assess":
|
|
@@ -5681,7 +5686,8 @@ var GoalWebSocketHandler = class {
|
|
|
5681
5686
|
}
|
|
5682
5687
|
case "goal.assignTask": {
|
|
5683
5688
|
const { taskId, agentId, agentName } = msg.payload;
|
|
5684
|
-
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
5689
|
+
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
5690
|
+
this.afterBoardMutation();
|
|
5685
5691
|
break;
|
|
5686
5692
|
}
|
|
5687
5693
|
case "goal.addTask": {
|
|
@@ -5726,7 +5732,10 @@ var GoalWebSocketHandler = class {
|
|
|
5726
5732
|
this.graph = graph;
|
|
5727
5733
|
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
5728
5734
|
} else {
|
|
5729
|
-
this.broadcast({
|
|
5735
|
+
this.broadcast({
|
|
5736
|
+
type: "goal.error",
|
|
5737
|
+
payload: { message: `Graph not found: ${graphId}` }
|
|
5738
|
+
});
|
|
5730
5739
|
}
|
|
5731
5740
|
}
|
|
5732
5741
|
break;
|
|
@@ -5749,10 +5758,13 @@ var GoalWebSocketHandler = class {
|
|
|
5749
5758
|
const mySeq = ++this.assessSeq;
|
|
5750
5759
|
const sendResult7 = (result) => {
|
|
5751
5760
|
if (mySeq !== this.assessSeq) return;
|
|
5752
|
-
sendSerialized(
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5761
|
+
sendSerialized(
|
|
5762
|
+
ws,
|
|
5763
|
+
JSON.stringify({
|
|
5764
|
+
type: "goal.assess.result",
|
|
5765
|
+
payload: { ...result, reqSeq: seq }
|
|
5766
|
+
})
|
|
5767
|
+
);
|
|
5756
5768
|
};
|
|
5757
5769
|
if (!goal.trim()) {
|
|
5758
5770
|
sendResult7({
|
|
@@ -5798,15 +5810,24 @@ var GoalWebSocketHandler = class {
|
|
|
5798
5810
|
const multiBoard = payload?.multiBoard ?? false;
|
|
5799
5811
|
const verifyTasks = payload?.verifyTasks ?? false;
|
|
5800
5812
|
const chimeraReview = payload?.chimeraReview ?? false;
|
|
5801
|
-
|
|
5813
|
+
const runAbort = new AbortController();
|
|
5814
|
+
this.abort = runAbort;
|
|
5802
5815
|
this.stopping = false;
|
|
5803
|
-
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal,
|
|
5804
|
-
if (this.stopping ||
|
|
5816
|
+
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal, runAbort.signal);
|
|
5817
|
+
if (this.stopping || runAbort.signal.aborted) {
|
|
5805
5818
|
this.broadcast({ type: "goal.stopped", payload: { title } });
|
|
5806
5819
|
return;
|
|
5807
5820
|
}
|
|
5808
5821
|
this.logger.info(`[Goal] Starting: ${title}`);
|
|
5809
|
-
const graph = await new PhaseGraphBuilder({
|
|
5822
|
+
const graph = await new PhaseGraphBuilder({
|
|
5823
|
+
title,
|
|
5824
|
+
description: goal,
|
|
5825
|
+
phases,
|
|
5826
|
+
autonomous,
|
|
5827
|
+
multiBoard,
|
|
5828
|
+
verifyTasks,
|
|
5829
|
+
chimeraReview
|
|
5830
|
+
}).build();
|
|
5810
5831
|
this.graph = graph;
|
|
5811
5832
|
await this.store.save(graph);
|
|
5812
5833
|
const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
|
|
@@ -5825,9 +5846,10 @@ var GoalWebSocketHandler = class {
|
|
|
5825
5846
|
maybeVerify.verifyPhase = (async (phase, env) => {
|
|
5826
5847
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
5827
5848
|
try {
|
|
5828
|
-
const {
|
|
5849
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
5829
5850
|
const result = await new Promise((resolve15) => {
|
|
5830
|
-
|
|
5851
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
5852
|
+
execFile2(npxCommand, ["tsc", "--noEmit"], { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
5831
5853
|
if (err && err.code === "ENOENT") {
|
|
5832
5854
|
resolve15("[verify] tsc not found \u2014 skipping");
|
|
5833
5855
|
return;
|
|
@@ -5969,11 +5991,41 @@ Run npx tsc --noEmit to verify the fix. Output the fixed file paths.`;
|
|
|
5969
5991
|
/** Generic fallback phases when the LLM planner produces nothing usable. */
|
|
5970
5992
|
defaultPhases() {
|
|
5971
5993
|
return [
|
|
5972
|
-
{
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5994
|
+
{
|
|
5995
|
+
name: "Discovery",
|
|
5996
|
+
description: "Requirements gathering",
|
|
5997
|
+
priority: "high",
|
|
5998
|
+
estimateHours: 2,
|
|
5999
|
+
parallelizable: false
|
|
6000
|
+
},
|
|
6001
|
+
{
|
|
6002
|
+
name: "Design",
|
|
6003
|
+
description: "Architecture and design",
|
|
6004
|
+
priority: "critical",
|
|
6005
|
+
estimateHours: 4,
|
|
6006
|
+
parallelizable: false
|
|
6007
|
+
},
|
|
6008
|
+
{
|
|
6009
|
+
name: "Implementation",
|
|
6010
|
+
description: "Core development",
|
|
6011
|
+
priority: "critical",
|
|
6012
|
+
estimateHours: 12,
|
|
6013
|
+
parallelizable: false
|
|
6014
|
+
},
|
|
6015
|
+
{
|
|
6016
|
+
name: "Testing",
|
|
6017
|
+
description: "Unit and integration tests",
|
|
6018
|
+
priority: "high",
|
|
6019
|
+
estimateHours: 6,
|
|
6020
|
+
parallelizable: true
|
|
6021
|
+
},
|
|
6022
|
+
{
|
|
6023
|
+
name: "Deployment",
|
|
6024
|
+
description: "Deploy to production",
|
|
6025
|
+
priority: "medium",
|
|
6026
|
+
estimateHours: 2,
|
|
6027
|
+
parallelizable: false
|
|
6028
|
+
}
|
|
5977
6029
|
];
|
|
5978
6030
|
}
|
|
5979
6031
|
/** Plan phases+todos for the goal via the LLM; fall back to defaults on failure.
|
|
@@ -6054,8 +6106,10 @@ Type: ${task.type}`;
|
|
|
6054
6106
|
try {
|
|
6055
6107
|
const result_ = await this.agent.run(reviewPrompt);
|
|
6056
6108
|
if (result_.status === "done" && result_.finalText) {
|
|
6057
|
-
this.logger.info(
|
|
6058
|
-
${
|
|
6109
|
+
this.logger.info(
|
|
6110
|
+
`[Goal] Chimera review for "${task.title}":
|
|
6111
|
+
${result_.finalText.slice(0, 2e3)}`
|
|
6112
|
+
);
|
|
6059
6113
|
}
|
|
6060
6114
|
} catch (err) {
|
|
6061
6115
|
this.logger.warn(`[Goal] Chimera review failed for "${task.title}": ${toErrorMessage2(err)}`);
|
|
@@ -6108,7 +6162,16 @@ ${result_.finalText.slice(0, 2e3)}`);
|
|
|
6108
6162
|
}
|
|
6109
6163
|
buildState(activePhaseId) {
|
|
6110
6164
|
if (!this.graph) {
|
|
6111
|
-
return {
|
|
6165
|
+
return {
|
|
6166
|
+
phases: [],
|
|
6167
|
+
tasks: [],
|
|
6168
|
+
overallPercent: 0,
|
|
6169
|
+
autonomous: true,
|
|
6170
|
+
title: "",
|
|
6171
|
+
multiBoard: false,
|
|
6172
|
+
verifyTasks: false,
|
|
6173
|
+
chimeraReview: false
|
|
6174
|
+
};
|
|
6112
6175
|
}
|
|
6113
6176
|
const phases = Array.from(this.graph.phases.values());
|
|
6114
6177
|
const currentActiveId = activePhaseId || phases.find((p) => p.status === "running")?.id || phases[0]?.id || "";
|
|
@@ -6606,9 +6669,9 @@ async function handleApiAnalyticsSummary(res) {
|
|
|
6606
6669
|
}
|
|
6607
6670
|
|
|
6608
6671
|
// src/server/http-server.ts
|
|
6609
|
-
import * as
|
|
6672
|
+
import * as fs10 from "node:fs/promises";
|
|
6610
6673
|
import * as http from "node:http";
|
|
6611
|
-
import * as
|
|
6674
|
+
import * as path13 from "node:path";
|
|
6612
6675
|
import * as v8 from "node:v8";
|
|
6613
6676
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6614
6677
|
|
|
@@ -7292,18 +7355,153 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7292
7355
|
}
|
|
7293
7356
|
}
|
|
7294
7357
|
|
|
7295
|
-
// src/server/
|
|
7358
|
+
// src/server/memory-diagnostics.ts
|
|
7296
7359
|
import * as fs8 from "node:fs/promises";
|
|
7297
7360
|
import * as path11 from "node:path";
|
|
7361
|
+
var DEFAULT_TAIL_BYTES = 1024 * 1024;
|
|
7362
|
+
var MAX_PROCESSES = 32;
|
|
7363
|
+
function finiteNumber(record, key) {
|
|
7364
|
+
const value = record[key];
|
|
7365
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7366
|
+
}
|
|
7367
|
+
function stringValue(record, key) {
|
|
7368
|
+
const value = record[key];
|
|
7369
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7370
|
+
}
|
|
7371
|
+
function toProcessDiagnostic(record) {
|
|
7372
|
+
const pid = finiteNumber(record, "pid");
|
|
7373
|
+
const ts = stringValue(record, "ts");
|
|
7374
|
+
if (pid === void 0 || ts === void 0) return null;
|
|
7375
|
+
const queueEntries = finiteNumber(record, "hqQueueEntries");
|
|
7376
|
+
const queueBytes = finiteNumber(record, "hqQueueBytes");
|
|
7377
|
+
const queueMaxBytes = finiteNumber(record, "hqQueueMaxBytes");
|
|
7378
|
+
const snapshotInFlight = finiteNumber(record, "hqSnapshotInFlight");
|
|
7379
|
+
const snapshotPending = finiteNumber(record, "hqSnapshotPending");
|
|
7380
|
+
const snapshotTimerScheduled = finiteNumber(record, "hqSnapshotTimerScheduled");
|
|
7381
|
+
const eventInFlight = finiteNumber(record, "hqEventInFlight");
|
|
7382
|
+
const eventPending = finiteNumber(record, "hqEventPending");
|
|
7383
|
+
const eventCoalesced = finiteNumber(record, "hqEventCoalesced");
|
|
7384
|
+
const eventDropped = finiteNumber(record, "hqEventDropped");
|
|
7385
|
+
return {
|
|
7386
|
+
pid,
|
|
7387
|
+
surface: stringValue(record, "surface") ?? "unknown",
|
|
7388
|
+
...stringValue(record, "sessionId") !== void 0 ? { sessionId: stringValue(record, "sessionId") } : {},
|
|
7389
|
+
ts,
|
|
7390
|
+
memory: {
|
|
7391
|
+
rss: finiteNumber(record, "rss") ?? 0,
|
|
7392
|
+
heapUsed: finiteNumber(record, "heapUsed") ?? 0,
|
|
7393
|
+
heapTotal: finiteNumber(record, "heapTotal") ?? 0,
|
|
7394
|
+
retainedHeapUsed: finiteNumber(record, "retainedHeapUsed"),
|
|
7395
|
+
nativeResidual: finiteNumber(record, "nativeResidual"),
|
|
7396
|
+
external: finiteNumber(record, "external"),
|
|
7397
|
+
arrayBuffers: finiteNumber(record, "arrayBuffers")
|
|
7398
|
+
},
|
|
7399
|
+
signal: stringValue(record, "memorySignal"),
|
|
7400
|
+
heapGrowthBytesPerHour: finiteNumber(record, "heapGrowthBytesPerHour"),
|
|
7401
|
+
rssGrowthBytesPerHour: finiteNumber(record, "rssGrowthBytesPerHour"),
|
|
7402
|
+
workload: {
|
|
7403
|
+
messages: finiteNumber(record, "messages"),
|
|
7404
|
+
messageEstimatedTokens: finiteNumber(record, "messageEstimatedTokens"),
|
|
7405
|
+
historyEntries: finiteNumber(record, "historyEntries"),
|
|
7406
|
+
historyMountedEntries: finiteNumber(record, "historyMountedEntries"),
|
|
7407
|
+
historyCachedGroups: finiteNumber(record, "historyCachedGroups"),
|
|
7408
|
+
appRenders: finiteNumber(record, "appRenders"),
|
|
7409
|
+
metricsDroppedObservations: finiteNumber(record, "metricsDroppedObservations"),
|
|
7410
|
+
kanbanSyncActive: finiteNumber(record, "kanbanSyncActive") === 1,
|
|
7411
|
+
kanbanSyncPendingBoards: finiteNumber(record, "kanbanSyncPendingBoards"),
|
|
7412
|
+
kanbanSyncFullRescanPending: finiteNumber(record, "kanbanSyncFullRescanPending") === 1,
|
|
7413
|
+
kanbanSyncRemoteApplyQueued: finiteNumber(record, "kanbanSyncRemoteApplyQueued") === 1,
|
|
7414
|
+
kanbanSyncPendingRemoteBoards: finiteNumber(record, "kanbanSyncPendingRemoteBoards"),
|
|
7415
|
+
kanbanSyncPublishRuns: finiteNumber(record, "kanbanSyncPublishRuns"),
|
|
7416
|
+
kanbanSyncCoalescedRefreshes: finiteNumber(record, "kanbanSyncCoalescedRefreshes"),
|
|
7417
|
+
kanbanSupervisorSnapshots: finiteNumber(record, "kanbanSupervisorSnapshots"),
|
|
7418
|
+
kanbanSupervisorScheduledBoards: finiteNumber(record, "kanbanSupervisorScheduledBoards"),
|
|
7419
|
+
kanbanSupervisorAgentCooldowns: finiteNumber(record, "kanbanSupervisorAgentCooldowns"),
|
|
7420
|
+
kanbanSupervisorRunningAgents: finiteNumber(record, "kanbanSupervisorRunningAgents")
|
|
7421
|
+
},
|
|
7422
|
+
resources: {
|
|
7423
|
+
active: finiteNumber(record, "activeResources"),
|
|
7424
|
+
types: stringValue(record, "activeResourceTypes")
|
|
7425
|
+
},
|
|
7426
|
+
...queueEntries !== void 0 && queueBytes !== void 0 && queueMaxBytes !== void 0 ? {
|
|
7427
|
+
hqQueue: {
|
|
7428
|
+
entries: queueEntries,
|
|
7429
|
+
bytes: queueBytes,
|
|
7430
|
+
maxBytes: queueMaxBytes,
|
|
7431
|
+
droppedFrames: finiteNumber(record, "hqQueueDroppedFrames") ?? 0,
|
|
7432
|
+
droppedBytes: finiteNumber(record, "hqQueueDroppedBytes") ?? 0,
|
|
7433
|
+
coalescedFrames: finiteNumber(record, "hqQueueCoalescedFrames") ?? 0,
|
|
7434
|
+
coalescedBytes: finiteNumber(record, "hqQueueCoalescedBytes") ?? 0
|
|
7435
|
+
}
|
|
7436
|
+
} : {},
|
|
7437
|
+
...snapshotInFlight !== void 0 || snapshotPending !== void 0 || snapshotTimerScheduled !== void 0 ? {
|
|
7438
|
+
hqSnapshot: {
|
|
7439
|
+
inFlight: snapshotInFlight === 1,
|
|
7440
|
+
pending: snapshotPending === 1,
|
|
7441
|
+
timerScheduled: snapshotTimerScheduled === 1,
|
|
7442
|
+
eventInFlight: eventInFlight === 1,
|
|
7443
|
+
pendingEvents: eventPending ?? 0,
|
|
7444
|
+
coalescedEvents: eventCoalesced ?? 0,
|
|
7445
|
+
droppedEvents: eventDropped ?? 0
|
|
7446
|
+
}
|
|
7447
|
+
} : {},
|
|
7448
|
+
profileTopStack: stringValue(record, "memoryProfileTopStack"),
|
|
7449
|
+
profileTopStackBytes: finiteNumber(record, "memoryProfileTopStackBytes")
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
async function readRecentProcessMemoryDiagnostics(globalRoot, tailBytes = DEFAULT_TAIL_BYTES) {
|
|
7453
|
+
if (!globalRoot) return [];
|
|
7454
|
+
const heapLog = path11.join(globalRoot, "logs", "heap.jsonl");
|
|
7455
|
+
let handle;
|
|
7456
|
+
try {
|
|
7457
|
+
handle = await fs8.open(heapLog, "r");
|
|
7458
|
+
const stat3 = await handle.stat();
|
|
7459
|
+
const length = Math.min(stat3.size, Math.max(1, tailBytes));
|
|
7460
|
+
if (length === 0) return [];
|
|
7461
|
+
const start = stat3.size - length;
|
|
7462
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
7463
|
+
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
7464
|
+
let text = buffer.subarray(0, bytesRead).toString("utf8");
|
|
7465
|
+
if (start > 0) {
|
|
7466
|
+
const firstNewline = text.indexOf("\n");
|
|
7467
|
+
if (firstNewline === -1) return [];
|
|
7468
|
+
text = text.slice(firstNewline + 1);
|
|
7469
|
+
}
|
|
7470
|
+
const newestByPid = /* @__PURE__ */ new Map();
|
|
7471
|
+
const lines = text.split(/\r?\n/u);
|
|
7472
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
7473
|
+
const line = lines[index]?.trim();
|
|
7474
|
+
if (!line) continue;
|
|
7475
|
+
try {
|
|
7476
|
+
const parsed = JSON.parse(line);
|
|
7477
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
|
|
7478
|
+
const diagnostic = toProcessDiagnostic(parsed);
|
|
7479
|
+
if (!diagnostic || newestByPid.has(diagnostic.pid)) continue;
|
|
7480
|
+
newestByPid.set(diagnostic.pid, diagnostic);
|
|
7481
|
+
if (newestByPid.size >= MAX_PROCESSES) break;
|
|
7482
|
+
} catch {
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
return [...newestByPid.values()].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
7486
|
+
} catch {
|
|
7487
|
+
return [];
|
|
7488
|
+
} finally {
|
|
7489
|
+
await handle?.close().catch(() => void 0);
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
// src/server/projects-manifest.ts
|
|
7494
|
+
import * as fs9 from "node:fs/promises";
|
|
7495
|
+
import * as path12 from "node:path";
|
|
7298
7496
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7299
7497
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7300
7498
|
function projectsJsonPath(globalConfigPath) {
|
|
7301
|
-
const base =
|
|
7302
|
-
return
|
|
7499
|
+
const base = path12.dirname(globalConfigPath);
|
|
7500
|
+
return path12.join(base, "projects.json");
|
|
7303
7501
|
}
|
|
7304
7502
|
async function loadManifest(globalConfigPath) {
|
|
7305
7503
|
try {
|
|
7306
|
-
const raw = await
|
|
7504
|
+
const raw = await fs9.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
7307
7505
|
const parsed = JSON.parse(raw);
|
|
7308
7506
|
return { projects: parsed.projects ?? [] };
|
|
7309
7507
|
} catch {
|
|
@@ -7312,37 +7510,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7312
7510
|
}
|
|
7313
7511
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7314
7512
|
const file = projectsJsonPath(globalConfigPath);
|
|
7315
|
-
await
|
|
7316
|
-
await
|
|
7513
|
+
await fs9.mkdir(path12.dirname(file), { recursive: true });
|
|
7514
|
+
await fs9.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7317
7515
|
}
|
|
7318
7516
|
function generateProjectSlug(rootPath) {
|
|
7319
7517
|
return projectSlug(rootPath);
|
|
7320
7518
|
}
|
|
7321
7519
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7322
|
-
const base =
|
|
7323
|
-
const dir =
|
|
7324
|
-
await
|
|
7520
|
+
const base = path12.dirname(globalConfigPath);
|
|
7521
|
+
const dir = path12.join(base, "projects", slug);
|
|
7522
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
7325
7523
|
return dir;
|
|
7326
7524
|
}
|
|
7327
7525
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7328
|
-
const root =
|
|
7526
|
+
const root = path12.resolve(options.projectRoot);
|
|
7329
7527
|
const file = projectsJsonPath(globalConfigPath);
|
|
7330
7528
|
let entry;
|
|
7331
7529
|
await withFileLock(file, async () => {
|
|
7332
7530
|
const manifest = await loadManifest(globalConfigPath);
|
|
7333
7531
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7334
|
-
entry = manifest.projects.find((candidate) =>
|
|
7532
|
+
entry = manifest.projects.find((candidate) => path12.resolve(candidate.root) === root);
|
|
7335
7533
|
if (entry) {
|
|
7336
7534
|
entry.lastSeen = now;
|
|
7337
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7535
|
+
if (options.workingDir) entry.lastWorkingDir = path12.resolve(options.workingDir);
|
|
7338
7536
|
} else {
|
|
7339
7537
|
entry = {
|
|
7340
|
-
name: options.name ??
|
|
7538
|
+
name: options.name ?? path12.basename(root),
|
|
7341
7539
|
root,
|
|
7342
7540
|
slug: generateProjectSlug(root),
|
|
7343
7541
|
createdAt: now,
|
|
7344
7542
|
lastSeen: now,
|
|
7345
|
-
lastWorkingDir: options.workingDir ?
|
|
7543
|
+
lastWorkingDir: options.workingDir ? path12.resolve(options.workingDir) : void 0
|
|
7346
7544
|
};
|
|
7347
7545
|
manifest.projects.push(entry);
|
|
7348
7546
|
}
|
|
@@ -7753,9 +7951,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
7753
7951
|
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'`;
|
|
7754
7952
|
}
|
|
7755
7953
|
function isInsideDist(candidate, distDir) {
|
|
7756
|
-
const root =
|
|
7757
|
-
const resolved =
|
|
7758
|
-
return resolved === root || resolved.startsWith(root +
|
|
7954
|
+
const root = path13.resolve(distDir);
|
|
7955
|
+
const resolved = path13.resolve(candidate);
|
|
7956
|
+
return resolved === root || resolved.startsWith(root + path13.sep);
|
|
7759
7957
|
}
|
|
7760
7958
|
function decodeSessionId(segment) {
|
|
7761
7959
|
try {
|
|
@@ -7775,7 +7973,7 @@ function strictDecodeParam(segment, res) {
|
|
|
7775
7973
|
}
|
|
7776
7974
|
function createHttpServer(opts) {
|
|
7777
7975
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
7778
|
-
const distDir =
|
|
7976
|
+
const distDir = path13.resolve(opts.distDir);
|
|
7779
7977
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
7780
7978
|
let techStackRuntime = null;
|
|
7781
7979
|
const getTechStackRuntime = async () => {
|
|
@@ -8103,11 +8301,7 @@ function createHttpServer(opts) {
|
|
|
8103
8301
|
if (researchMatch && req.method === "POST") {
|
|
8104
8302
|
const pkg = strictDecodeParam(researchMatch[1], res);
|
|
8105
8303
|
if (pkg === null) return;
|
|
8106
|
-
await handleTechStackDependencyResearch(
|
|
8107
|
-
res,
|
|
8108
|
-
deps2,
|
|
8109
|
-
pkg
|
|
8110
|
-
);
|
|
8304
|
+
await handleTechStackDependencyResearch(res, deps2, pkg);
|
|
8111
8305
|
return;
|
|
8112
8306
|
}
|
|
8113
8307
|
} catch (error2) {
|
|
@@ -8143,6 +8337,7 @@ function createHttpServer(opts) {
|
|
|
8143
8337
|
return;
|
|
8144
8338
|
}
|
|
8145
8339
|
if (url.pathname === "/debug/system" && req.method === "GET") {
|
|
8340
|
+
const processes = await readRecentProcessMemoryDiagnostics(opts.globalRoot);
|
|
8146
8341
|
res.writeHead(200, {
|
|
8147
8342
|
"Content-Type": "application/json",
|
|
8148
8343
|
"Cache-Control": "no-store"
|
|
@@ -8155,6 +8350,7 @@ function createHttpServer(opts) {
|
|
|
8155
8350
|
uptime: process.uptime(),
|
|
8156
8351
|
cpuUsage: process.cpuUsage(),
|
|
8157
8352
|
codebaseIndexServer: getIndexState2().server,
|
|
8353
|
+
processes,
|
|
8158
8354
|
timestamp: Date.now()
|
|
8159
8355
|
})
|
|
8160
8356
|
);
|
|
@@ -8162,24 +8358,24 @@ function createHttpServer(opts) {
|
|
|
8162
8358
|
}
|
|
8163
8359
|
let filePath;
|
|
8164
8360
|
if (url.pathname === "/" || url.pathname === "") {
|
|
8165
|
-
filePath =
|
|
8361
|
+
filePath = path13.join(distDir, "index.html");
|
|
8166
8362
|
} else {
|
|
8167
|
-
filePath =
|
|
8363
|
+
filePath = path13.join(distDir, url.pathname);
|
|
8168
8364
|
}
|
|
8169
|
-
const resolvedPath =
|
|
8365
|
+
const resolvedPath = path13.resolve(filePath);
|
|
8170
8366
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
8171
8367
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
8172
8368
|
res.end("Forbidden");
|
|
8173
8369
|
return;
|
|
8174
8370
|
}
|
|
8175
|
-
const ext =
|
|
8371
|
+
const ext = path13.extname(resolvedPath);
|
|
8176
8372
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
8177
8373
|
res.setHeader("Content-Type", contentType);
|
|
8178
8374
|
setStaticSecurityHeaders(res);
|
|
8179
8375
|
if (ext === ".html") {
|
|
8180
8376
|
if (!shouldSetAuthCookie) res.setHeader("Cache-Control", "no-cache");
|
|
8181
8377
|
res.setHeader("Content-Security-Policy", buildCspHeader(opts.publicWsUrl, opts.host, port));
|
|
8182
|
-
const html = await
|
|
8378
|
+
const html = await fs10.readFile(resolvedPath, "utf8");
|
|
8183
8379
|
res.writeHead(200);
|
|
8184
8380
|
res.end(injectWsConfig(html, { publicWsUrl: opts.publicWsUrl }));
|
|
8185
8381
|
return;
|
|
@@ -8190,13 +8386,13 @@ function createHttpServer(opts) {
|
|
|
8190
8386
|
url.pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600"
|
|
8191
8387
|
);
|
|
8192
8388
|
}
|
|
8193
|
-
const fileContent = await
|
|
8389
|
+
const fileContent = await fs10.readFile(resolvedPath);
|
|
8194
8390
|
res.writeHead(200);
|
|
8195
8391
|
res.end(fileContent);
|
|
8196
8392
|
} catch (err) {
|
|
8197
8393
|
if (err.code === "ENOENT") {
|
|
8198
8394
|
try {
|
|
8199
|
-
const html = await
|
|
8395
|
+
const html = await fs10.readFile(path13.join(distDir, "index.html"), "utf8");
|
|
8200
8396
|
setStaticSecurityHeaders(res);
|
|
8201
8397
|
res.writeHead(200, {
|
|
8202
8398
|
"Content-Type": "text/html",
|
|
@@ -8226,14 +8422,14 @@ function createHttpServer(opts) {
|
|
|
8226
8422
|
|
|
8227
8423
|
// src/server/instance-registry.ts
|
|
8228
8424
|
import * as os from "node:os";
|
|
8229
|
-
import * as
|
|
8230
|
-
import * as
|
|
8425
|
+
import * as path14 from "node:path";
|
|
8426
|
+
import * as fs11 from "node:fs/promises";
|
|
8231
8427
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8232
8428
|
function defaultBaseDir() {
|
|
8233
|
-
return
|
|
8429
|
+
return path14.join(os.homedir(), ".wrongstack");
|
|
8234
8430
|
}
|
|
8235
8431
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8236
|
-
return
|
|
8432
|
+
return path14.join(baseDir, "webui-instances.json");
|
|
8237
8433
|
}
|
|
8238
8434
|
function isPidAlive(pid) {
|
|
8239
8435
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -8246,7 +8442,7 @@ function isPidAlive(pid) {
|
|
|
8246
8442
|
}
|
|
8247
8443
|
async function load(file) {
|
|
8248
8444
|
try {
|
|
8249
|
-
const raw = await
|
|
8445
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
8250
8446
|
const parsed = JSON.parse(raw);
|
|
8251
8447
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
8252
8448
|
return parsed;
|
|
@@ -10044,14 +10240,14 @@ function registerShutdownHandlers(res) {
|
|
|
10044
10240
|
}
|
|
10045
10241
|
|
|
10046
10242
|
// src/server/config-doctor.ts
|
|
10047
|
-
import * as
|
|
10243
|
+
import * as fs12 from "node:fs/promises";
|
|
10048
10244
|
import {
|
|
10049
10245
|
repairConfigDefaults
|
|
10050
10246
|
} from "@wrongstack/core/storage";
|
|
10051
10247
|
import { atomicWrite as atomicWrite5 } from "@wrongstack/core/utils";
|
|
10052
10248
|
import { decryptConfigSecrets } from "@wrongstack/core/security";
|
|
10053
10249
|
async function readConfig(file, vault) {
|
|
10054
|
-
const raw = await
|
|
10250
|
+
const raw = await fs12.readFile(file, "utf8");
|
|
10055
10251
|
const parsed = JSON.parse(raw);
|
|
10056
10252
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10057
10253
|
throw new Error("Active profile config must contain a JSON object.");
|
|
@@ -10115,6 +10311,7 @@ async function handleConfigDoctor(ws, apply, deps2) {
|
|
|
10115
10311
|
|
|
10116
10312
|
// src/server/mailbox-handlers.ts
|
|
10117
10313
|
import {
|
|
10314
|
+
actionToAckInput,
|
|
10118
10315
|
getSharedProjectMailbox,
|
|
10119
10316
|
isMailboxMessageVisibleTo,
|
|
10120
10317
|
MAILBOX_TYPE_PROPERTIES,
|
|
@@ -10131,6 +10328,43 @@ function getMailboxForDeps(deps2) {
|
|
|
10131
10328
|
const dir = resolveProjectDir(projectRoot, globalRoot);
|
|
10132
10329
|
return getSharedProjectMailbox(dir, deps2.events);
|
|
10133
10330
|
}
|
|
10331
|
+
async function handleMailboxAction(ws, deps2, payload) {
|
|
10332
|
+
const mb = getMailboxForDeps(deps2);
|
|
10333
|
+
if (!mb) {
|
|
10334
|
+
send(ws, {
|
|
10335
|
+
type: "mailbox.action_result",
|
|
10336
|
+
payload: {
|
|
10337
|
+
requestId: payload.requestId,
|
|
10338
|
+
success: false,
|
|
10339
|
+
error: "No project root available"
|
|
10340
|
+
}
|
|
10341
|
+
});
|
|
10342
|
+
return;
|
|
10343
|
+
}
|
|
10344
|
+
try {
|
|
10345
|
+
const message = payload.action === "soft-delete" ? await mb.softDelete(payload.mailId, payload.readerId) : await mb.ack(actionToAckInput(payload.action, payload));
|
|
10346
|
+
send(ws, {
|
|
10347
|
+
type: "mailbox.action_result",
|
|
10348
|
+
payload: {
|
|
10349
|
+
requestId: payload.requestId,
|
|
10350
|
+
success: message !== null,
|
|
10351
|
+
action: payload.action,
|
|
10352
|
+
mailId: payload.mailId
|
|
10353
|
+
}
|
|
10354
|
+
});
|
|
10355
|
+
} catch (err) {
|
|
10356
|
+
send(ws, {
|
|
10357
|
+
type: "mailbox.action_result",
|
|
10358
|
+
payload: {
|
|
10359
|
+
requestId: payload.requestId,
|
|
10360
|
+
success: false,
|
|
10361
|
+
action: payload.action,
|
|
10362
|
+
mailId: payload.mailId,
|
|
10363
|
+
error: errMessage(err)
|
|
10364
|
+
}
|
|
10365
|
+
});
|
|
10366
|
+
}
|
|
10367
|
+
}
|
|
10134
10368
|
async function handleMailboxSend(ws, deps2, payload) {
|
|
10135
10369
|
const mb = getMailboxForDeps(deps2);
|
|
10136
10370
|
if (!mb) {
|
|
@@ -10146,7 +10380,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10146
10380
|
}
|
|
10147
10381
|
try {
|
|
10148
10382
|
const message = await mb.send({
|
|
10149
|
-
from: "webui",
|
|
10383
|
+
from: payload.from ?? "webui",
|
|
10150
10384
|
to: payload.to,
|
|
10151
10385
|
type: payload.type,
|
|
10152
10386
|
audience: payload.audience,
|
|
@@ -10161,6 +10395,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10161
10395
|
requestId: payload.requestId,
|
|
10162
10396
|
success: true,
|
|
10163
10397
|
messageId: message.id,
|
|
10398
|
+
from: message.from,
|
|
10164
10399
|
to: message.to,
|
|
10165
10400
|
audience: message.audience ?? "all"
|
|
10166
10401
|
}
|
|
@@ -10205,6 +10440,7 @@ async function handleMailboxMessages(ws, deps2, payload) {
|
|
|
10205
10440
|
send(ws, {
|
|
10206
10441
|
type: "mailbox.messages",
|
|
10207
10442
|
payload: {
|
|
10443
|
+
...payload?.unreadOnly === true ? { unreadOnly: true } : {},
|
|
10208
10444
|
messages: visibleMessages.map((m) => {
|
|
10209
10445
|
const readByMe = payload?.agentId !== void 0 ? payload.agentId in m.readBy : false;
|
|
10210
10446
|
const completedByMe = payload?.agentId !== void 0 ? m.completedBy === payload.agentId : false;
|
|
@@ -10320,6 +10556,14 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10320
10556
|
...ctx.events ? { events: ctx.events } : {}
|
|
10321
10557
|
};
|
|
10322
10558
|
return {
|
|
10559
|
+
action: (ws, msg) => {
|
|
10560
|
+
const parsed = validateMailboxActionPayload(msg.payload);
|
|
10561
|
+
if (!parsed.ok) {
|
|
10562
|
+
sendResult2(ws, false, parsed.message);
|
|
10563
|
+
return;
|
|
10564
|
+
}
|
|
10565
|
+
return handleMailboxAction(ws, deps2, parsed.value);
|
|
10566
|
+
},
|
|
10323
10567
|
send: (ws, msg) => {
|
|
10324
10568
|
const parsed = validateMailboxSendPayload(msg.payload);
|
|
10325
10569
|
if (!parsed.ok) {
|
|
@@ -10370,6 +10614,9 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10370
10614
|
}
|
|
10371
10615
|
async function handleMailboxRoute(ws, msg, handlers) {
|
|
10372
10616
|
switch (msg.type) {
|
|
10617
|
+
case "mailbox.action":
|
|
10618
|
+
await handlers.action(ws, msg);
|
|
10619
|
+
return true;
|
|
10373
10620
|
case "mailbox.send":
|
|
10374
10621
|
await handlers.send(ws, msg);
|
|
10375
10622
|
return true;
|
|
@@ -11916,8 +12163,8 @@ function seedContextMeta(config, context) {
|
|
|
11916
12163
|
}
|
|
11917
12164
|
|
|
11918
12165
|
// src/server/pref-helpers.ts
|
|
11919
|
-
import * as
|
|
11920
|
-
import * as
|
|
12166
|
+
import * as fs13 from "node:fs/promises";
|
|
12167
|
+
import * as path15 from "node:path";
|
|
11921
12168
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
11922
12169
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
11923
12170
|
var PREF_KEYS = [
|
|
@@ -12013,11 +12260,11 @@ function prefSnapshot(contextMeta) {
|
|
|
12013
12260
|
return snapshot;
|
|
12014
12261
|
}
|
|
12015
12262
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
12016
|
-
const globalRoot =
|
|
12263
|
+
const globalRoot = path15.dirname(filePath);
|
|
12017
12264
|
await backupConfigFile(filePath, { globalRoot });
|
|
12018
12265
|
let raw;
|
|
12019
12266
|
try {
|
|
12020
|
-
raw = await
|
|
12267
|
+
raw = await fs13.readFile(filePath, "utf8");
|
|
12021
12268
|
} catch {
|
|
12022
12269
|
raw = "{}";
|
|
12023
12270
|
}
|
|
@@ -12492,8 +12739,8 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
12492
12739
|
}
|
|
12493
12740
|
|
|
12494
12741
|
// src/server/project-handlers.ts
|
|
12495
|
-
import * as
|
|
12496
|
-
import * as
|
|
12742
|
+
import * as fs14 from "node:fs/promises";
|
|
12743
|
+
import * as path16 from "node:path";
|
|
12497
12744
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
12498
12745
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
12499
12746
|
function createProjectHandlers(ctx) {
|
|
@@ -12545,10 +12792,10 @@ function createProjectHandlers(ctx) {
|
|
|
12545
12792
|
});
|
|
12546
12793
|
return;
|
|
12547
12794
|
}
|
|
12548
|
-
const resolved =
|
|
12549
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12795
|
+
const resolved = path16.resolve(parsed.value.root);
|
|
12796
|
+
const name2 = parsed.value.name?.trim() || path16.basename(resolved);
|
|
12550
12797
|
try {
|
|
12551
|
-
const stat3 = await
|
|
12798
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
12552
12799
|
if (!stat3?.isDirectory()) {
|
|
12553
12800
|
sendTo(ws, {
|
|
12554
12801
|
type: "projects.added",
|
|
@@ -12557,7 +12804,7 @@ function createProjectHandlers(ctx) {
|
|
|
12557
12804
|
return;
|
|
12558
12805
|
}
|
|
12559
12806
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
12560
|
-
const already = before.projects.some((project) =>
|
|
12807
|
+
const already = before.projects.some((project) => path16.resolve(project.root) === resolved);
|
|
12561
12808
|
const entry = await touchProjectInManifest(
|
|
12562
12809
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
12563
12810
|
ctx.globalConfigPath
|
|
@@ -12587,8 +12834,8 @@ function createProjectHandlers(ctx) {
|
|
|
12587
12834
|
});
|
|
12588
12835
|
return;
|
|
12589
12836
|
}
|
|
12590
|
-
const resolved =
|
|
12591
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12837
|
+
const resolved = path16.resolve(parsed.value.root);
|
|
12838
|
+
const name2 = parsed.value.name?.trim() || path16.basename(resolved);
|
|
12592
12839
|
if (!ctx.allowProjectMutations) {
|
|
12593
12840
|
sendTo(ws, {
|
|
12594
12841
|
type: "projects.selected",
|
|
@@ -12601,7 +12848,7 @@ function createProjectHandlers(ctx) {
|
|
|
12601
12848
|
return;
|
|
12602
12849
|
}
|
|
12603
12850
|
try {
|
|
12604
|
-
const stat3 = await
|
|
12851
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
12605
12852
|
if (!stat3?.isDirectory()) {
|
|
12606
12853
|
sendTo(ws, {
|
|
12607
12854
|
type: "projects.selected",
|
|
@@ -12631,7 +12878,7 @@ function createProjectHandlers(ctx) {
|
|
|
12631
12878
|
const previousIdentityTarget = {
|
|
12632
12879
|
projectSlug: previousPaths.projectSlug,
|
|
12633
12880
|
projectRoot: previousProjectRoot,
|
|
12634
|
-
projectName:
|
|
12881
|
+
projectName: path16.basename(previousProjectRoot),
|
|
12635
12882
|
workingDir: ctx.context.workingDir
|
|
12636
12883
|
};
|
|
12637
12884
|
const previousUsage = ctx.tokenCounter.total();
|
|
@@ -12794,14 +13041,14 @@ async function searchCatalogModels(registry, rawQuery, limit = 8) {
|
|
|
12794
13041
|
}
|
|
12795
13042
|
|
|
12796
13043
|
// src/server/provider-config-io.ts
|
|
12797
|
-
import * as
|
|
13044
|
+
import * as fs15 from "node:fs/promises";
|
|
12798
13045
|
import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
|
|
12799
13046
|
import { atomicWrite as atomicWrite7 } from "@wrongstack/core/utils";
|
|
12800
13047
|
import { decryptConfigSecrets as decryptConfigSecrets3, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
12801
13048
|
async function loadSavedProviders(configPath, vault) {
|
|
12802
13049
|
let raw;
|
|
12803
13050
|
try {
|
|
12804
|
-
raw = await
|
|
13051
|
+
raw = await fs15.readFile(configPath, "utf8");
|
|
12805
13052
|
} catch {
|
|
12806
13053
|
return {};
|
|
12807
13054
|
}
|
|
@@ -12819,7 +13066,7 @@ async function saveProviders(configPath, vault, providers, profileConfigPath) {
|
|
|
12819
13066
|
let raw;
|
|
12820
13067
|
let fileExists = true;
|
|
12821
13068
|
try {
|
|
12822
|
-
raw = await
|
|
13069
|
+
raw = await fs15.readFile(targetPath, "utf8");
|
|
12823
13070
|
} catch (err) {
|
|
12824
13071
|
if (err.code !== "ENOENT") {
|
|
12825
13072
|
throw new ConfigError2({
|
|
@@ -13522,6 +13769,7 @@ var CLIENT_COLLABORATION_MESSAGE_TYPES = [
|
|
|
13522
13769
|
"collab.resume",
|
|
13523
13770
|
"collab.grant_control",
|
|
13524
13771
|
"collab.inject_tool",
|
|
13772
|
+
"mailbox.action",
|
|
13525
13773
|
"mailbox.agents",
|
|
13526
13774
|
"mailbox.clear",
|
|
13527
13775
|
"mailbox.compact",
|
|
@@ -13787,6 +14035,7 @@ var SERVER_COLLABORATION_MESSAGE_TYPES = [
|
|
|
13787
14035
|
"collab.pause.granted",
|
|
13788
14036
|
"collab.pause.released",
|
|
13789
14037
|
"collab.state",
|
|
14038
|
+
"mailbox.action_result",
|
|
13790
14039
|
"mailbox.agent_registered",
|
|
13791
14040
|
"mailbox.agents",
|
|
13792
14041
|
"mailbox.cleared",
|
|
@@ -14010,13 +14259,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
14010
14259
|
// src/protocol/decoder.ts
|
|
14011
14260
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
14012
14261
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
14013
|
-
function inspectValue(value,
|
|
14262
|
+
function inspectValue(value, path30, depth) {
|
|
14014
14263
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
14015
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
14264
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path30 };
|
|
14016
14265
|
}
|
|
14017
14266
|
if (value === null || typeof value !== "object") return null;
|
|
14018
14267
|
for (const key of Object.keys(value)) {
|
|
14019
|
-
const childPath = `${
|
|
14268
|
+
const childPath = `${path30}.${key}`;
|
|
14020
14269
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
14021
14270
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
14022
14271
|
}
|
|
@@ -15712,8 +15961,8 @@ function createRouteFamilyDispatcher(options) {
|
|
|
15712
15961
|
}
|
|
15713
15962
|
|
|
15714
15963
|
// src/server/shell-open.ts
|
|
15715
|
-
import * as
|
|
15716
|
-
import * as
|
|
15964
|
+
import * as fs16 from "node:fs/promises";
|
|
15965
|
+
import * as path17 from "node:path";
|
|
15717
15966
|
import { spawn } from "node:child_process";
|
|
15718
15967
|
function normalizeShellOpenTarget(target) {
|
|
15719
15968
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -15724,11 +15973,11 @@ function shellQuote(s) {
|
|
|
15724
15973
|
}
|
|
15725
15974
|
async function handleShellOpen(req, logger, options) {
|
|
15726
15975
|
try {
|
|
15727
|
-
const resolved =
|
|
15976
|
+
const resolved = path17.resolve(req.path);
|
|
15728
15977
|
if (options?.projectRoot) {
|
|
15729
|
-
const root =
|
|
15730
|
-
const relative5 =
|
|
15731
|
-
const escapes = relative5.startsWith("..") ||
|
|
15978
|
+
const root = path17.resolve(options.projectRoot);
|
|
15979
|
+
const relative5 = path17.relative(root, resolved);
|
|
15980
|
+
const escapes = relative5.startsWith("..") || path17.isAbsolute(relative5);
|
|
15732
15981
|
if (escapes) {
|
|
15733
15982
|
return {
|
|
15734
15983
|
success: false,
|
|
@@ -15736,7 +15985,7 @@ async function handleShellOpen(req, logger, options) {
|
|
|
15736
15985
|
};
|
|
15737
15986
|
}
|
|
15738
15987
|
}
|
|
15739
|
-
await
|
|
15988
|
+
await fs16.access(resolved);
|
|
15740
15989
|
if (METACHAR_REGEX.test(resolved)) {
|
|
15741
15990
|
return { success: false, message: "Path contains unsupported characters." };
|
|
15742
15991
|
}
|
|
@@ -15787,7 +16036,12 @@ async function handleShellOpen(req, logger, options) {
|
|
|
15787
16036
|
}
|
|
15788
16037
|
|
|
15789
16038
|
// src/server/sdd-board-ws-handler.ts
|
|
15790
|
-
import {
|
|
16039
|
+
import {
|
|
16040
|
+
enqueueKanbanWorkflowCommand,
|
|
16041
|
+
kanbanWorkflowId,
|
|
16042
|
+
listBoards as listBoards3,
|
|
16043
|
+
listKanbanWorkflowStates
|
|
16044
|
+
} from "@wrongstack/kanban";
|
|
15791
16045
|
import {
|
|
15792
16046
|
applySddLifecycle,
|
|
15793
16047
|
extractVerificationCommand,
|
|
@@ -15814,7 +16068,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15814
16068
|
clients = /* @__PURE__ */ new Set();
|
|
15815
16069
|
lifecycle;
|
|
15816
16070
|
security;
|
|
15817
|
-
|
|
16071
|
+
standalonePollingEnabled;
|
|
15818
16072
|
latest = null;
|
|
15819
16073
|
poll = null;
|
|
15820
16074
|
pollInFlight = false;
|
|
@@ -15823,7 +16077,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15823
16077
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
15824
16078
|
this.lifecycle = lifecycle;
|
|
15825
16079
|
this.security = security;
|
|
15826
|
-
this.
|
|
16080
|
+
this.standalonePollingEnabled = events === void 0;
|
|
15827
16081
|
if (events) {
|
|
15828
16082
|
const handler = (e) => {
|
|
15829
16083
|
this.latest = e.snapshot;
|
|
@@ -15858,7 +16112,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15858
16112
|
return;
|
|
15859
16113
|
}
|
|
15860
16114
|
if (msg.type === "sdd.board.list") {
|
|
15861
|
-
const boards = await this.
|
|
16115
|
+
const boards = await this.listBoardEntries();
|
|
15862
16116
|
this.broadcast({ type: "sdd.board.list", payload: { boards } });
|
|
15863
16117
|
return;
|
|
15864
16118
|
}
|
|
@@ -15871,7 +16125,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
15871
16125
|
const verificationCommands = [];
|
|
15872
16126
|
if (action === "set_task_verification") {
|
|
15873
16127
|
const command = msg.payload?.verificationCommand;
|
|
15874
|
-
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
16128
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
16129
|
+
return;
|
|
15875
16130
|
if (typeof command === "string" && command.trim()) {
|
|
15876
16131
|
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
15877
16132
|
}
|
|
@@ -15905,18 +16160,26 @@ var SddBoardWebSocketHandler = class {
|
|
|
15905
16160
|
);
|
|
15906
16161
|
if (!authorization.allowed) return;
|
|
15907
16162
|
}
|
|
15908
|
-
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.
|
|
16163
|
+
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.listBoardEntries())[0]?.runId;
|
|
15909
16164
|
if (runId) {
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
16165
|
+
if (this.lifecycle && this.lifecycle.controlTransport !== "legacy-file") {
|
|
16166
|
+
await enqueueKanbanWorkflowCommand(
|
|
16167
|
+
this.lifecycle.projectRoot,
|
|
16168
|
+
kanbanWorkflowId("sdd", runId),
|
|
16169
|
+
{ type: action, payload: msg.payload }
|
|
16170
|
+
);
|
|
16171
|
+
} else {
|
|
16172
|
+
await this.store.appendControl(runId, {
|
|
16173
|
+
ts: Date.now(),
|
|
16174
|
+
type: action,
|
|
16175
|
+
payload: msg.payload
|
|
16176
|
+
});
|
|
16177
|
+
}
|
|
15915
16178
|
}
|
|
15916
16179
|
}
|
|
15917
16180
|
}
|
|
15918
16181
|
/**
|
|
15919
|
-
* Apply a cleanup/rollback/destroy from
|
|
16182
|
+
* Apply a cleanup/rollback/destroy from durable state and broadcast a structured
|
|
15920
16183
|
* `sdd.board.lifecycle_result`. Refuses (no-op) while a run is still active —
|
|
15921
16184
|
* the user must stop it first; the UI gates the buttons on `!active` and the
|
|
15922
16185
|
* Destroy flow auto-stops then waits before sending `destroy`.
|
|
@@ -15925,7 +16188,11 @@ var SddBoardWebSocketHandler = class {
|
|
|
15925
16188
|
if (!this.lifecycle) {
|
|
15926
16189
|
this.broadcast({
|
|
15927
16190
|
type: "sdd.board.lifecycle_result",
|
|
15928
|
-
payload: {
|
|
16191
|
+
payload: {
|
|
16192
|
+
op,
|
|
16193
|
+
ok: false,
|
|
16194
|
+
reason: "Lifecycle operations are not available in this session."
|
|
16195
|
+
}
|
|
15929
16196
|
});
|
|
15930
16197
|
return;
|
|
15931
16198
|
}
|
|
@@ -15941,6 +16208,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15941
16208
|
projectRoot: this.lifecycle.projectRoot,
|
|
15942
16209
|
paths: this.lifecycle.paths,
|
|
15943
16210
|
runId,
|
|
16211
|
+
stateTransport: this.lifecycle.stateTransport ?? "kanban",
|
|
15944
16212
|
revertMerged: payload?.revertMerged === true
|
|
15945
16213
|
});
|
|
15946
16214
|
this.broadcast({ type: "sdd.board.lifecycle_result", payload: result });
|
|
@@ -15969,22 +16237,27 @@ var SddBoardWebSocketHandler = class {
|
|
|
15969
16237
|
if (this.pollInFlight) return;
|
|
15970
16238
|
this.pollInFlight = true;
|
|
15971
16239
|
try {
|
|
15972
|
-
const
|
|
15973
|
-
if (!
|
|
15974
|
-
if (this.latest && this.latest.updatedAt >=
|
|
16240
|
+
const snap = await this.loadLatestSnapshot();
|
|
16241
|
+
if (!snap) return;
|
|
16242
|
+
if (this.latest && this.latest.updatedAt >= snap.updatedAt && this.latest.runId === snap.runId) {
|
|
15975
16243
|
return;
|
|
15976
16244
|
}
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
16245
|
+
this.latest = snap;
|
|
16246
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
16247
|
+
} catch (err) {
|
|
16248
|
+
console.warn(
|
|
16249
|
+
JSON.stringify({
|
|
16250
|
+
level: "warn",
|
|
16251
|
+
event: "sdd_board.poll_failed",
|
|
16252
|
+
message: err instanceof Error ? err.message : String(err)
|
|
16253
|
+
})
|
|
16254
|
+
);
|
|
15982
16255
|
} finally {
|
|
15983
16256
|
this.pollInFlight = false;
|
|
15984
16257
|
}
|
|
15985
16258
|
}
|
|
15986
16259
|
startPolling() {
|
|
15987
|
-
if (!this.
|
|
16260
|
+
if (!this.standalonePollingEnabled || this.poll !== null || this.clients.size === 0) return;
|
|
15988
16261
|
this.poll = setInterval(() => void this.pollLatest(), 1e3);
|
|
15989
16262
|
this.poll.unref?.();
|
|
15990
16263
|
}
|
|
@@ -15994,17 +16267,44 @@ var SddBoardWebSocketHandler = class {
|
|
|
15994
16267
|
this.poll = null;
|
|
15995
16268
|
}
|
|
15996
16269
|
async sendCurrent(client) {
|
|
15997
|
-
const snap = this.latest ?? await this.
|
|
16270
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
15998
16271
|
if (snap) this.send(client, { type: "sdd.board.snapshot", payload: snap });
|
|
15999
16272
|
}
|
|
16000
16273
|
async broadcastCurrent() {
|
|
16001
|
-
const snap = this.latest ?? await this.
|
|
16274
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
16002
16275
|
if (snap) this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
16003
16276
|
}
|
|
16004
|
-
async
|
|
16277
|
+
async loadLatestSnapshot() {
|
|
16278
|
+
if (this.usesKanbanState()) {
|
|
16279
|
+
const states = await listKanbanWorkflowStates(this.lifecycle.projectRoot, "sdd:");
|
|
16280
|
+
const snapshots = states.map((state) => state.value).filter(isSddBoardSnapshot).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
16281
|
+
return snapshots[0] ?? null;
|
|
16282
|
+
}
|
|
16005
16283
|
const entry = await this.store.latest();
|
|
16006
16284
|
return entry ? this.store.load(entry.runId) : null;
|
|
16007
16285
|
}
|
|
16286
|
+
async listBoardEntries() {
|
|
16287
|
+
if (!this.usesKanbanState()) return this.store.list();
|
|
16288
|
+
const states = await listKanbanWorkflowStates(this.lifecycle.projectRoot, "sdd:");
|
|
16289
|
+
return states.flatMap((state) => {
|
|
16290
|
+
if (!isSddBoardSnapshot(state.value)) return [];
|
|
16291
|
+
const snapshot = state.value;
|
|
16292
|
+
return [
|
|
16293
|
+
{
|
|
16294
|
+
runId: snapshot.runId,
|
|
16295
|
+
...snapshot.specId ? { specId: snapshot.specId } : {},
|
|
16296
|
+
title: snapshot.title,
|
|
16297
|
+
status: snapshot.status,
|
|
16298
|
+
total: snapshot.progress.total,
|
|
16299
|
+
completed: snapshot.progress.completed,
|
|
16300
|
+
updatedAt: snapshot.updatedAt
|
|
16301
|
+
}
|
|
16302
|
+
];
|
|
16303
|
+
});
|
|
16304
|
+
}
|
|
16305
|
+
usesKanbanState() {
|
|
16306
|
+
return Boolean(this.lifecycle && this.lifecycle.stateTransport !== "legacy-file");
|
|
16307
|
+
}
|
|
16008
16308
|
broadcast(msg) {
|
|
16009
16309
|
const data = JSON.stringify(msg);
|
|
16010
16310
|
for (const client of this.clients) {
|
|
@@ -16015,17 +16315,20 @@ var SddBoardWebSocketHandler = class {
|
|
|
16015
16315
|
sendSerialized(client.ws, JSON.stringify(msg));
|
|
16016
16316
|
}
|
|
16017
16317
|
};
|
|
16318
|
+
function isSddBoardSnapshot(value) {
|
|
16319
|
+
if (!value || typeof value !== "object") return false;
|
|
16320
|
+
const snapshot = value;
|
|
16321
|
+
return typeof snapshot.runId === "string" && typeof snapshot.title === "string" && typeof snapshot.status === "string" && typeof snapshot.updatedAt === "number" && Array.isArray(snapshot.tasks) && Boolean(snapshot.progress && typeof snapshot.progress.total === "number");
|
|
16322
|
+
}
|
|
16018
16323
|
|
|
16019
16324
|
// src/server/sdd-wizard-wiring.ts
|
|
16020
|
-
import * as
|
|
16021
|
-
import {
|
|
16022
|
-
DefaultTaskStore,
|
|
16023
|
-
TaskTracker
|
|
16024
|
-
} from "@wrongstack/core/tasking";
|
|
16325
|
+
import * as path18 from "node:path";
|
|
16326
|
+
import { DefaultTaskStore, TaskTracker } from "@wrongstack/core/tasking";
|
|
16025
16327
|
import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
16026
16328
|
import { WorktreeManager as WorktreeManager2 } from "@wrongstack/core/worktree";
|
|
16027
16329
|
import {
|
|
16028
16330
|
cleanupStaleSddWorktrees,
|
|
16331
|
+
createKanbanSddSessionPersistence,
|
|
16029
16332
|
decomposeNonAtomicTasks,
|
|
16030
16333
|
gatherProjectContext,
|
|
16031
16334
|
makeAcceptanceCriteriaVerifier,
|
|
@@ -16043,7 +16346,7 @@ import {
|
|
|
16043
16346
|
} from "@wrongstack/sdd";
|
|
16044
16347
|
async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
16045
16348
|
const runTracker = tracker ?? (() => {
|
|
16046
|
-
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
16349
|
+
const t = new TaskTracker({ store: deps2.taskStore ?? new DefaultTaskStore() });
|
|
16047
16350
|
t.setGraph(graph);
|
|
16048
16351
|
return t;
|
|
16049
16352
|
})();
|
|
@@ -16052,7 +16355,8 @@ async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
|
16052
16355
|
if (worktreesEnabled && await isGitWorkTree(deps2.projectRoot)) {
|
|
16053
16356
|
void cleanupStaleSddWorktrees({
|
|
16054
16357
|
projectRoot: deps2.projectRoot,
|
|
16055
|
-
boardsDir: deps2.projectSddBoards
|
|
16358
|
+
boardsDir: deps2.projectSddBoards,
|
|
16359
|
+
stateTransport: "kanban"
|
|
16056
16360
|
}).catch(() => void 0);
|
|
16057
16361
|
worktrees = new WorktreeManager2({
|
|
16058
16362
|
projectRoot: deps2.projectRoot,
|
|
@@ -16131,7 +16435,8 @@ function buildSddWizardDeps(opts) {
|
|
|
16131
16435
|
}).catch(() => {
|
|
16132
16436
|
projectContext = "";
|
|
16133
16437
|
});
|
|
16134
|
-
const
|
|
16438
|
+
const legacySessionPath = opts.paths.projectSddSession ?? path18.join(opts.paths.projectDir, "sdd-session.json");
|
|
16439
|
+
const sessionPersistence = createKanbanSddSessionPersistence(opts.projectRoot, legacySessionPath);
|
|
16135
16440
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
16136
16441
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
16137
16442
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -16152,7 +16457,7 @@ function buildSddWizardDeps(opts) {
|
|
|
16152
16457
|
const makeDriver = () => new SddInterviewDriver({
|
|
16153
16458
|
specStore,
|
|
16154
16459
|
graphStore,
|
|
16155
|
-
|
|
16460
|
+
sessionPersistence,
|
|
16156
16461
|
projectContext
|
|
16157
16462
|
});
|
|
16158
16463
|
const launchFromGraph = async (graph, config, tracker) => {
|
|
@@ -16164,6 +16469,7 @@ function buildSddWizardDeps(opts) {
|
|
|
16164
16469
|
projectRoot: opts.projectRoot,
|
|
16165
16470
|
subagentFactory: opts.subagentFactory,
|
|
16166
16471
|
projectSddBoards: opts.paths.projectSddBoards,
|
|
16472
|
+
taskStore: graphStore,
|
|
16167
16473
|
registry,
|
|
16168
16474
|
runIsolatedTurn,
|
|
16169
16475
|
...opts.brain ? { brain: opts.brain } : {}
|
|
@@ -16233,8 +16539,18 @@ var SddWizardWebSocketHandler = class {
|
|
|
16233
16539
|
lastAgentText = "";
|
|
16234
16540
|
/** Guards against overlapping interview turns (one in flight at a time). */
|
|
16235
16541
|
busy = false;
|
|
16236
|
-
/**
|
|
16542
|
+
/** Set when authoritative session state could not be read during bootstrap. */
|
|
16543
|
+
resumeError = null;
|
|
16544
|
+
/** Resolves once project-context gather + durable resume finish. */
|
|
16237
16545
|
ready;
|
|
16546
|
+
/**
|
|
16547
|
+
* Single-flight slot for the resume probe. `handleMessage` awaits this
|
|
16548
|
+
* when `resumeError` is set, so concurrent frames cannot race the retry
|
|
16549
|
+
* — one probe either clears the gate or re-latches it, then every
|
|
16550
|
+
* queued frame re-checks and proceeds. `null` when no probe is in
|
|
16551
|
+
* flight (or after the probe has settled).
|
|
16552
|
+
*/
|
|
16553
|
+
resumeProbe = null;
|
|
16238
16554
|
async bootstrap() {
|
|
16239
16555
|
try {
|
|
16240
16556
|
await this.deps.ensureReady?.();
|
|
@@ -16242,7 +16558,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
16242
16558
|
}
|
|
16243
16559
|
await this.tryResume();
|
|
16244
16560
|
}
|
|
16245
|
-
/** Rehydrate a persisted interview if one exists
|
|
16561
|
+
/** Rehydrate a persisted interview if one exists. */
|
|
16246
16562
|
async tryResume() {
|
|
16247
16563
|
if (this.driver) return;
|
|
16248
16564
|
try {
|
|
@@ -16251,9 +16567,11 @@ var SddWizardWebSocketHandler = class {
|
|
|
16251
16567
|
this.driver = driver;
|
|
16252
16568
|
this.lastAgentText = driver.getLastAgentText() ?? "";
|
|
16253
16569
|
}
|
|
16254
|
-
|
|
16570
|
+
this.resumeError = null;
|
|
16571
|
+
} catch (error2) {
|
|
16255
16572
|
this.driver = null;
|
|
16256
16573
|
this.lastAgentText = "";
|
|
16574
|
+
this.resumeError = error2 instanceof Error ? error2.message : String(error2);
|
|
16257
16575
|
}
|
|
16258
16576
|
}
|
|
16259
16577
|
addClient(ws) {
|
|
@@ -16262,6 +16580,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
16262
16580
|
ws.on("close", () => this.clients.delete(client));
|
|
16263
16581
|
ws.on("error", () => this.clients.delete(client));
|
|
16264
16582
|
void this.ready.then(() => {
|
|
16583
|
+
if (this.resumeError) {
|
|
16584
|
+
this.send(client, { type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
16585
|
+
return;
|
|
16586
|
+
}
|
|
16265
16587
|
if (this.driver) {
|
|
16266
16588
|
this.send(client, this.snapshotMsg());
|
|
16267
16589
|
if (this.lastAgentText) {
|
|
@@ -16273,6 +16595,22 @@ var SddWizardWebSocketHandler = class {
|
|
|
16273
16595
|
async handleMessage(msg) {
|
|
16274
16596
|
try {
|
|
16275
16597
|
await this.ready;
|
|
16598
|
+
if (this.resumeError) {
|
|
16599
|
+
if (this.resumeProbe === null) {
|
|
16600
|
+
const probe = Promise.resolve().then(() => this.tryResume());
|
|
16601
|
+
this.resumeProbe = probe;
|
|
16602
|
+
void probe.finally(() => {
|
|
16603
|
+
setTimeout(() => {
|
|
16604
|
+
if (this.resumeProbe === probe) this.resumeProbe = null;
|
|
16605
|
+
}, 0);
|
|
16606
|
+
});
|
|
16607
|
+
}
|
|
16608
|
+
await this.resumeProbe;
|
|
16609
|
+
if (this.resumeError) {
|
|
16610
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
16611
|
+
return;
|
|
16612
|
+
}
|
|
16613
|
+
}
|
|
16276
16614
|
switch (msg.type) {
|
|
16277
16615
|
case "sdd.spec.start":
|
|
16278
16616
|
await this.onStart(String(msg.payload?.goal ?? "").trim(), {
|
|
@@ -16289,7 +16627,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
16289
16627
|
if (this.driver) {
|
|
16290
16628
|
this.broadcast(this.snapshotMsg());
|
|
16291
16629
|
if (this.lastAgentText) {
|
|
16292
|
-
this.broadcast({
|
|
16630
|
+
this.broadcast({
|
|
16631
|
+
type: "sdd.spec.agent_text",
|
|
16632
|
+
payload: { text: this.lastAgentText }
|
|
16633
|
+
});
|
|
16293
16634
|
}
|
|
16294
16635
|
}
|
|
16295
16636
|
break;
|
|
@@ -16503,10 +16844,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
16503
16844
|
|
|
16504
16845
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
16505
16846
|
import { watch as fsWatch } from "node:fs";
|
|
16506
|
-
import * as
|
|
16847
|
+
import * as path19 from "node:path";
|
|
16507
16848
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
16508
16849
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
16509
|
-
const globalRoot = globalConfigPath ?
|
|
16850
|
+
const globalRoot = globalConfigPath ? path19.dirname(globalConfigPath) : void 0;
|
|
16510
16851
|
if (!globalRoot) return void 0;
|
|
16511
16852
|
const disposers = [];
|
|
16512
16853
|
const broadcastSessions = async () => {
|
|
@@ -16516,8 +16857,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
16516
16857
|
const sessions = await registry.list();
|
|
16517
16858
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
16518
16859
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
16519
|
-
const myRoot =
|
|
16520
|
-
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug :
|
|
16860
|
+
const myRoot = path19.resolve(context.projectRoot);
|
|
16861
|
+
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path19.resolve(s.projectRoot) === myRoot).map((s) => ({
|
|
16521
16862
|
sessionId: s.sessionId,
|
|
16522
16863
|
projectName: s.projectName,
|
|
16523
16864
|
projectSlug: s.projectSlug,
|
|
@@ -16707,14 +17048,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
16707
17048
|
|
|
16708
17049
|
// src/server/setup-events-status-watcher.ts
|
|
16709
17050
|
import { watch as fsWatch2 } from "node:fs";
|
|
16710
|
-
import * as
|
|
16711
|
-
import * as
|
|
17051
|
+
import * as fs17 from "node:fs/promises";
|
|
17052
|
+
import * as path21 from "node:path";
|
|
16712
17053
|
|
|
16713
17054
|
// src/server/setup-events-watcher.ts
|
|
16714
|
-
import * as
|
|
17055
|
+
import * as path20 from "node:path";
|
|
16715
17056
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
16716
17057
|
const raw = String(filename);
|
|
16717
|
-
const relative5 =
|
|
17058
|
+
const relative5 = path20.isAbsolute(raw) ? path20.relative(projectsDir, raw) : raw;
|
|
16718
17059
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
16719
17060
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
16720
17061
|
return parts.at(-2) ?? null;
|
|
@@ -16749,7 +17090,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
16749
17090
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
16750
17091
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
16751
17092
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
16752
|
-
const projectsDir =
|
|
17093
|
+
const projectsDir = path21.join(wpaths.globalRoot, "projects");
|
|
16753
17094
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
16754
17095
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
16755
17096
|
const DEBOUNCE_MS = 150;
|
|
@@ -16794,7 +17135,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16794
17135
|
let watcher;
|
|
16795
17136
|
const startWatcher = async () => {
|
|
16796
17137
|
try {
|
|
16797
|
-
await
|
|
17138
|
+
await fs17.mkdir(projectsDir, { recursive: true });
|
|
16798
17139
|
if (isDisposed()) return;
|
|
16799
17140
|
watcher = fsWatch2(
|
|
16800
17141
|
projectsDir,
|
|
@@ -16808,8 +17149,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16808
17149
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
16809
17150
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
16810
17151
|
try {
|
|
16811
|
-
const targetFile =
|
|
16812
|
-
const content = await
|
|
17152
|
+
const targetFile = path21.join(projectsDir, projectHash, "status.json");
|
|
17153
|
+
const content = await fs17.readFile(targetFile, "utf-8");
|
|
16813
17154
|
const statusData = JSON.parse(content);
|
|
16814
17155
|
scheduleBroadcast(projectHash, statusData);
|
|
16815
17156
|
} catch {
|
|
@@ -16866,8 +17207,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16866
17207
|
}
|
|
16867
17208
|
|
|
16868
17209
|
// src/server/setup-events-core-watchers.ts
|
|
16869
|
-
import * as
|
|
16870
|
-
import * as
|
|
17210
|
+
import * as fs18 from "node:fs/promises";
|
|
17211
|
+
import * as path22 from "node:path";
|
|
16871
17212
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
16872
17213
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
16873
17214
|
const disposers = [];
|
|
@@ -16903,9 +17244,9 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
16903
17244
|
if (wpaths?.projectStatus) {
|
|
16904
17245
|
try {
|
|
16905
17246
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
16906
|
-
const dir =
|
|
16907
|
-
await
|
|
16908
|
-
await
|
|
17247
|
+
const dir = path22.dirname(statusFile);
|
|
17248
|
+
await fs18.mkdir(dir, { recursive: true });
|
|
17249
|
+
await fs18.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
16909
17250
|
} catch (err) {
|
|
16910
17251
|
console.error(
|
|
16911
17252
|
JSON.stringify({
|
|
@@ -17930,7 +18271,7 @@ var SpecsWebSocketHandler = class {
|
|
|
17930
18271
|
// src/server/start-webui.ts
|
|
17931
18272
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17932
18273
|
import * as http2 from "node:http";
|
|
17933
|
-
import * as
|
|
18274
|
+
import * as path29 from "node:path";
|
|
17934
18275
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
17935
18276
|
import { getSharedProjectMailbox as getSharedProjectMailbox4, resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
|
|
17936
18277
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
@@ -17944,7 +18285,7 @@ import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } fro
|
|
|
17944
18285
|
import {
|
|
17945
18286
|
expectDefined as expectDefined3,
|
|
17946
18287
|
sessionScopedPath as sessionScopedPath3,
|
|
17947
|
-
|
|
18288
|
+
startSharedHeapWatchdog,
|
|
17948
18289
|
toErrorMessage as toErrorMessage13,
|
|
17949
18290
|
wstackGlobalRoot as wstackGlobalRoot2
|
|
17950
18291
|
} from "@wrongstack/core/utils";
|
|
@@ -17953,10 +18294,8 @@ import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
|
17953
18294
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
17954
18295
|
|
|
17955
18296
|
// src/server/backend-services.ts
|
|
17956
|
-
import { join as
|
|
17957
|
-
import {
|
|
17958
|
-
Agent
|
|
17959
|
-
} from "@wrongstack/core/agent";
|
|
18297
|
+
import { join as join13 } from "node:path";
|
|
18298
|
+
import { Agent } from "@wrongstack/core/agent";
|
|
17960
18299
|
import {
|
|
17961
18300
|
BrainDecisionLedger,
|
|
17962
18301
|
BrainMonitor,
|
|
@@ -17968,8 +18307,8 @@ import {
|
|
|
17968
18307
|
mailboxSessionTag,
|
|
17969
18308
|
ObservableBrainArbiter as ObservableBrainArbiterCtor
|
|
17970
18309
|
} from "@wrongstack/core/coordination";
|
|
17971
|
-
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
17972
18310
|
import { DEFAULT_TOOLS_CONFIG } from "@wrongstack/core/defaults";
|
|
18311
|
+
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
17973
18312
|
import {
|
|
17974
18313
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
17975
18314
|
createBrainRuntime,
|
|
@@ -17979,7 +18318,9 @@ import {
|
|
|
17979
18318
|
} from "@wrongstack/core/execution";
|
|
17980
18319
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
17981
18320
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
17982
|
-
import {
|
|
18321
|
+
import {
|
|
18322
|
+
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
18323
|
+
} from "@wrongstack/core/types";
|
|
17983
18324
|
import {
|
|
17984
18325
|
estimateRequestTokensCalibrated,
|
|
17985
18326
|
toErrorMessage as toErrorMessage9
|
|
@@ -17996,7 +18337,7 @@ import {
|
|
|
17996
18337
|
import { spawn as spawn2 } from "node:child_process";
|
|
17997
18338
|
import { createRequire } from "node:module";
|
|
17998
18339
|
import { existsSync } from "node:fs";
|
|
17999
|
-
import { dirname as dirname7, join as
|
|
18340
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
18000
18341
|
import { resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core/coordination";
|
|
18001
18342
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
18002
18343
|
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
@@ -18125,7 +18466,7 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
18125
18466
|
function findWorkspaceCliEntry(projectRoot) {
|
|
18126
18467
|
let dir = projectRoot;
|
|
18127
18468
|
for (let i = 0; i < 6; i++) {
|
|
18128
|
-
const candidate =
|
|
18469
|
+
const candidate = join11(dir, "packages", "cli", "dist", "index.js");
|
|
18129
18470
|
if (existsSync(candidate)) return candidate;
|
|
18130
18471
|
const parent = dirname7(dir);
|
|
18131
18472
|
if (parent === dir) return null;
|
|
@@ -18370,10 +18711,10 @@ function clampDim(value, fallback) {
|
|
|
18370
18711
|
}
|
|
18371
18712
|
|
|
18372
18713
|
// src/server/worktree-ws-handler.ts
|
|
18373
|
-
import { join as
|
|
18714
|
+
import { join as join12, resolve as resolve12, sep as sep5 } from "node:path";
|
|
18715
|
+
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
18374
18716
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
18375
18717
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
18376
|
-
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
18377
18718
|
var MAX_ACTIVITY = 6;
|
|
18378
18719
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
|
|
18379
18720
|
var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
|
|
@@ -18392,6 +18733,20 @@ var WorktreeWebSocketHandler = class {
|
|
|
18392
18733
|
baseBranch = "";
|
|
18393
18734
|
broadcastInterval = null;
|
|
18394
18735
|
offs = [];
|
|
18736
|
+
/**
|
|
18737
|
+
* Single-flight guard for orphan scans. Two concurrent `scanAndBroadcast()`
|
|
18738
|
+
* calls would race: the first call reads `wt.listManaged()`, then the
|
|
18739
|
+
* second call's mutation completes (e.g. `removeOne`), then the first call
|
|
18740
|
+
* broadcasts a stale orphan list — the user sees a row persist and clicks
|
|
18741
|
+
* Remove a second time, hitting "remove failed (not a managed worktree?)".
|
|
18742
|
+
*
|
|
18743
|
+
* We coalesce concurrent triggers: while a scan is in flight, additional
|
|
18744
|
+
* callers are remembered as "need a re-scan" and exactly one follow-up
|
|
18745
|
+
* scan runs after the in-flight one finishes. The re-scan picks up any
|
|
18746
|
+
* mutations that landed during the previous scan's `listManaged()` window.
|
|
18747
|
+
*/
|
|
18748
|
+
scanInFlight = null;
|
|
18749
|
+
scanRescanNeeded = false;
|
|
18395
18750
|
addClient(ws) {
|
|
18396
18751
|
this.clients.add(ws);
|
|
18397
18752
|
ws.on("close", () => this.clients.delete(ws));
|
|
@@ -18410,7 +18765,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
18410
18765
|
return true;
|
|
18411
18766
|
}
|
|
18412
18767
|
if (msg.type === "worktree.remove") {
|
|
18413
|
-
await this.removeOne(
|
|
18768
|
+
await this.removeOne(
|
|
18769
|
+
msg.payload?.["dir"],
|
|
18770
|
+
msg.payload?.["branch"]
|
|
18771
|
+
);
|
|
18414
18772
|
return true;
|
|
18415
18773
|
}
|
|
18416
18774
|
if (msg.type === "worktree.merge") {
|
|
@@ -18418,7 +18776,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
18418
18776
|
return true;
|
|
18419
18777
|
}
|
|
18420
18778
|
if (msg.type === "worktree.diff") {
|
|
18421
|
-
await this.diffOne(
|
|
18779
|
+
await this.diffOne(
|
|
18780
|
+
msg.payload?.["dir"],
|
|
18781
|
+
msg.payload?.["baseBranch"]
|
|
18782
|
+
);
|
|
18422
18783
|
return true;
|
|
18423
18784
|
}
|
|
18424
18785
|
return false;
|
|
@@ -18431,7 +18792,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
18431
18792
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
18432
18793
|
/** Absolute managed-worktrees root for this project. */
|
|
18433
18794
|
worktreesRoot() {
|
|
18434
|
-
return resolve12(
|
|
18795
|
+
return resolve12(join12(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
18435
18796
|
}
|
|
18436
18797
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
18437
18798
|
underRoot(dir) {
|
|
@@ -18447,12 +18808,43 @@ var WorktreeWebSocketHandler = class {
|
|
|
18447
18808
|
}
|
|
18448
18809
|
return live;
|
|
18449
18810
|
}
|
|
18811
|
+
/**
|
|
18812
|
+
* Coalesced orphan scan entry point. Multiple concurrent callers share one
|
|
18813
|
+
* in-flight scan; if a caller arrives during a scan, a single follow-up
|
|
18814
|
+
* scan is scheduled to pick up mutations that landed mid-scan. Returns the
|
|
18815
|
+
* shared promise so `await this.scanAndBroadcast()` still works for callers
|
|
18816
|
+
* that want to chain after the next broadcast.
|
|
18817
|
+
*/
|
|
18818
|
+
scanAndBroadcast() {
|
|
18819
|
+
if (this.scanInFlight) {
|
|
18820
|
+
this.scanRescanNeeded = true;
|
|
18821
|
+
return this.scanInFlight;
|
|
18822
|
+
}
|
|
18823
|
+
this.scanInFlight = this.runScanAndMaybeRescan();
|
|
18824
|
+
return this.scanInFlight;
|
|
18825
|
+
}
|
|
18826
|
+
/**
|
|
18827
|
+
* Run the actual scan, then drain a pending re-scan request if one came in
|
|
18828
|
+
* during the scan. The re-scan is bounded (one follow-up) — additional
|
|
18829
|
+
* requests that arrive during the follow-up coalesce into the next one.
|
|
18830
|
+
*/
|
|
18831
|
+
async runScanAndMaybeRescan() {
|
|
18832
|
+
try {
|
|
18833
|
+
await this.runScanOnce();
|
|
18834
|
+
while (this.scanRescanNeeded) {
|
|
18835
|
+
this.scanRescanNeeded = false;
|
|
18836
|
+
await this.runScanOnce();
|
|
18837
|
+
}
|
|
18838
|
+
} finally {
|
|
18839
|
+
this.scanInFlight = null;
|
|
18840
|
+
}
|
|
18841
|
+
}
|
|
18450
18842
|
/**
|
|
18451
18843
|
* Scan the disk for managed worktrees/branches NOT owned by a live in-session
|
|
18452
18844
|
* run and broadcast them as orphans, with whether it is safe to clean now.
|
|
18453
18845
|
* No-op (empty inventory) when management deps were not wired.
|
|
18454
18846
|
*/
|
|
18455
|
-
async
|
|
18847
|
+
async runScanOnce() {
|
|
18456
18848
|
if (!this.management) {
|
|
18457
18849
|
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
18458
18850
|
return;
|
|
@@ -18508,7 +18900,8 @@ var WorktreeWebSocketHandler = class {
|
|
|
18508
18900
|
}
|
|
18509
18901
|
const res = await cleanupStaleSddWorktrees2({
|
|
18510
18902
|
projectRoot: this.management.projectRoot,
|
|
18511
|
-
boardsDir: this.management.boardsDir
|
|
18903
|
+
boardsDir: this.management.boardsDir,
|
|
18904
|
+
stateTransport: "kanban"
|
|
18512
18905
|
});
|
|
18513
18906
|
if (res.skippedReason) {
|
|
18514
18907
|
this.broadcast({
|
|
@@ -18521,26 +18914,45 @@ var WorktreeWebSocketHandler = class {
|
|
|
18521
18914
|
for (const [id, h] of [...this.handles]) {
|
|
18522
18915
|
if (!ACTIVE_STATUSES.has(h.status)) this.handles.delete(id);
|
|
18523
18916
|
}
|
|
18524
|
-
this.broadcast({
|
|
18917
|
+
this.broadcast({
|
|
18918
|
+
type: "worktree.cleanup_result",
|
|
18919
|
+
payload: { ok: true, removed: res.removed }
|
|
18920
|
+
});
|
|
18525
18921
|
this.broadcastState();
|
|
18526
18922
|
await this.scanAndBroadcast();
|
|
18527
18923
|
}
|
|
18528
18924
|
/** Remove/discard ONE worktree + branch. Refused while a live run owns it. */
|
|
18529
18925
|
async removeOne(dir, branch) {
|
|
18530
18926
|
if (!this.management || !dir && !branch) {
|
|
18531
|
-
this.broadcast({
|
|
18927
|
+
this.broadcast({
|
|
18928
|
+
type: "worktree.cleanup_result",
|
|
18929
|
+
payload: { ok: false, removed: 0, reason: "nothing to remove" }
|
|
18930
|
+
});
|
|
18532
18931
|
return;
|
|
18533
18932
|
}
|
|
18534
18933
|
if (branch && !MANAGED_BRANCH_RE.test(branch)) {
|
|
18535
|
-
this.broadcast({
|
|
18934
|
+
this.broadcast({
|
|
18935
|
+
type: "worktree.cleanup_result",
|
|
18936
|
+
payload: { ok: false, removed: 0, reason: "not a managed worktree branch" }
|
|
18937
|
+
});
|
|
18536
18938
|
return;
|
|
18537
18939
|
}
|
|
18538
18940
|
if (dir && !this.underRoot(dir)) {
|
|
18539
|
-
this.broadcast({
|
|
18941
|
+
this.broadcast({
|
|
18942
|
+
type: "worktree.cleanup_result",
|
|
18943
|
+
payload: { ok: false, removed: 0, reason: "path is outside the managed worktrees root" }
|
|
18944
|
+
});
|
|
18540
18945
|
return;
|
|
18541
18946
|
}
|
|
18542
18947
|
if (branch && this.liveActiveBranches().has(branch)) {
|
|
18543
|
-
this.broadcast({
|
|
18948
|
+
this.broadcast({
|
|
18949
|
+
type: "worktree.cleanup_result",
|
|
18950
|
+
payload: {
|
|
18951
|
+
ok: false,
|
|
18952
|
+
removed: 0,
|
|
18953
|
+
reason: "a run is live on this worktree \u2014 stop it first"
|
|
18954
|
+
}
|
|
18955
|
+
});
|
|
18544
18956
|
return;
|
|
18545
18957
|
}
|
|
18546
18958
|
let removed = false;
|
|
@@ -18549,31 +18961,54 @@ var WorktreeWebSocketHandler = class {
|
|
|
18549
18961
|
({ removed } = await wt.removeOne(dir, branch));
|
|
18550
18962
|
}
|
|
18551
18963
|
for (const [id, h] of [...this.handles]) {
|
|
18552
|
-
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId))
|
|
18964
|
+
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId))
|
|
18965
|
+
this.handles.delete(id);
|
|
18553
18966
|
}
|
|
18554
|
-
this.broadcast({
|
|
18967
|
+
this.broadcast({
|
|
18968
|
+
type: "worktree.cleanup_result",
|
|
18969
|
+
payload: {
|
|
18970
|
+
ok: removed,
|
|
18971
|
+
removed: removed ? 1 : 0,
|
|
18972
|
+
reason: removed ? void 0 : "remove failed (not a managed worktree?)"
|
|
18973
|
+
}
|
|
18974
|
+
});
|
|
18555
18975
|
this.broadcastState();
|
|
18556
18976
|
await this.scanAndBroadcast();
|
|
18557
18977
|
}
|
|
18558
18978
|
/** Squash-merge ONE branch into base. Refused while a live run owns it. */
|
|
18559
18979
|
async mergeBranch(branch) {
|
|
18560
18980
|
if (!this.management || !branch) {
|
|
18561
|
-
this.broadcast({
|
|
18981
|
+
this.broadcast({
|
|
18982
|
+
type: "worktree.merge_result",
|
|
18983
|
+
payload: { ok: false, branch: branch ?? "", reason: "no branch" }
|
|
18984
|
+
});
|
|
18562
18985
|
return;
|
|
18563
18986
|
}
|
|
18564
18987
|
if (!MANAGED_BRANCH_RE.test(branch)) {
|
|
18565
|
-
this.broadcast({
|
|
18988
|
+
this.broadcast({
|
|
18989
|
+
type: "worktree.merge_result",
|
|
18990
|
+
payload: { ok: false, branch, reason: "not a managed worktree branch" }
|
|
18991
|
+
});
|
|
18566
18992
|
return;
|
|
18567
18993
|
}
|
|
18568
18994
|
if (this.liveActiveBranches().has(branch)) {
|
|
18569
|
-
this.broadcast({
|
|
18995
|
+
this.broadcast({
|
|
18996
|
+
type: "worktree.merge_result",
|
|
18997
|
+
payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" }
|
|
18998
|
+
});
|
|
18570
18999
|
return;
|
|
18571
19000
|
}
|
|
18572
19001
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
18573
19002
|
const res = await wt.mergeBranch(branch);
|
|
18574
19003
|
this.broadcast({
|
|
18575
19004
|
type: "worktree.merge_result",
|
|
18576
|
-
payload: {
|
|
19005
|
+
payload: {
|
|
19006
|
+
ok: res.ok,
|
|
19007
|
+
branch,
|
|
19008
|
+
conflict: res.conflict,
|
|
19009
|
+
conflictFiles: res.conflictFiles,
|
|
19010
|
+
reason: res.reason
|
|
19011
|
+
}
|
|
18577
19012
|
});
|
|
18578
19013
|
await this.scanAndBroadcast();
|
|
18579
19014
|
}
|
|
@@ -18615,8 +19050,14 @@ var WorktreeWebSocketHandler = class {
|
|
|
18615
19050
|
}),
|
|
18616
19051
|
on("worktree.committed", (p) => {
|
|
18617
19052
|
const e = p;
|
|
18618
|
-
this.patch(e.handleId, {
|
|
18619
|
-
|
|
19053
|
+
this.patch(e.handleId, {
|
|
19054
|
+
status: "committing",
|
|
19055
|
+
insertions: e.insertions,
|
|
19056
|
+
deletions: e.deletions,
|
|
19057
|
+
files: e.files
|
|
19058
|
+
});
|
|
19059
|
+
if (e.committed)
|
|
19060
|
+
this.activity(e.handleId, "committed", `+${e.insertions}/-${e.deletions} (${e.files}f)`);
|
|
18620
19061
|
this.broadcastState();
|
|
18621
19062
|
}),
|
|
18622
19063
|
on("worktree.merged", (p) => {
|
|
@@ -18657,10 +19098,15 @@ var WorktreeWebSocketHandler = class {
|
|
|
18657
19098
|
activity(id, kind, text) {
|
|
18658
19099
|
const cur = this.handles.get(id);
|
|
18659
19100
|
if (cur) {
|
|
18660
|
-
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(
|
|
19101
|
+
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(
|
|
19102
|
+
-MAX_ACTIVITY
|
|
19103
|
+
);
|
|
18661
19104
|
this.handles.set(id, { ...cur, recentActivity });
|
|
18662
19105
|
}
|
|
18663
|
-
this.broadcast({
|
|
19106
|
+
this.broadcast({
|
|
19107
|
+
type: "worktree.event",
|
|
19108
|
+
payload: { kind, handleId: id, text, at: Date.now() }
|
|
19109
|
+
});
|
|
18664
19110
|
}
|
|
18665
19111
|
stateMessage() {
|
|
18666
19112
|
return {
|
|
@@ -18902,7 +19348,7 @@ async function createAgentServices(input) {
|
|
|
18902
19348
|
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
18903
19349
|
fallbackModels: config.fallbackModels
|
|
18904
19350
|
});
|
|
18905
|
-
const brainLedgerPath =
|
|
19351
|
+
const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
|
|
18906
19352
|
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
18907
19353
|
let brainLedger;
|
|
18908
19354
|
const startBrainLedger = () => {
|
|
@@ -18967,33 +19413,35 @@ async function createAgentServices(input) {
|
|
|
18967
19413
|
brainLog.push(entry);
|
|
18968
19414
|
if (brainLog.length > 20) brainLog.shift();
|
|
18969
19415
|
};
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
19416
|
+
const brainLogOffs = [
|
|
19417
|
+
events.on(
|
|
19418
|
+
"brain.decision_answered",
|
|
19419
|
+
(e) => pushBrainLog({
|
|
19420
|
+
at: e.at,
|
|
19421
|
+
kind: "answered",
|
|
19422
|
+
question: e.request.question,
|
|
19423
|
+
outcome: e.decision.type === "answer" ? e.decision.optionId ?? e.decision.text : ""
|
|
19424
|
+
})
|
|
19425
|
+
),
|
|
19426
|
+
events.on(
|
|
19427
|
+
"brain.decision_ask_human",
|
|
19428
|
+
(e) => pushBrainLog({
|
|
19429
|
+
at: e.at,
|
|
19430
|
+
kind: "ask_human",
|
|
19431
|
+
question: e.request.question,
|
|
19432
|
+
outcome: "needs human judgement"
|
|
19433
|
+
})
|
|
19434
|
+
),
|
|
19435
|
+
events.on(
|
|
19436
|
+
"brain.decision_denied",
|
|
19437
|
+
(e) => pushBrainLog({
|
|
19438
|
+
at: e.at,
|
|
19439
|
+
kind: "denied",
|
|
19440
|
+
question: e.request.question,
|
|
19441
|
+
outcome: e.decision.type === "deny" ? e.decision.reason : ""
|
|
19442
|
+
})
|
|
19443
|
+
)
|
|
19444
|
+
];
|
|
18997
19445
|
const brainMailbox = getSharedProjectMailbox2(wpaths.projectDir, events);
|
|
18998
19446
|
brainMonitor = new BrainMonitor({
|
|
18999
19447
|
events,
|
|
@@ -19105,6 +19553,17 @@ async function createAgentServices(input) {
|
|
|
19105
19553
|
getActiveSessionId: () => context.session.id
|
|
19106
19554
|
}
|
|
19107
19555
|
);
|
|
19556
|
+
let realtimeHandlersDisposed = false;
|
|
19557
|
+
const disposeRealtimeHandlers = () => {
|
|
19558
|
+
if (realtimeHandlersDisposed) return;
|
|
19559
|
+
realtimeHandlersDisposed = true;
|
|
19560
|
+
for (const off of brainLogOffs) off();
|
|
19561
|
+
goalHandler.dispose();
|
|
19562
|
+
sddBoardHandler.dispose();
|
|
19563
|
+
worktreeHandler.dispose();
|
|
19564
|
+
terminalHandler.dispose();
|
|
19565
|
+
collabHandler.dispose();
|
|
19566
|
+
};
|
|
19108
19567
|
return {
|
|
19109
19568
|
collabBus,
|
|
19110
19569
|
compactor,
|
|
@@ -19130,6 +19589,7 @@ async function createAgentServices(input) {
|
|
|
19130
19589
|
worktreeHandler,
|
|
19131
19590
|
terminalHandler,
|
|
19132
19591
|
collabHandler,
|
|
19592
|
+
disposeRealtimeHandlers,
|
|
19133
19593
|
updateAutoCompactionMaxContext
|
|
19134
19594
|
};
|
|
19135
19595
|
}
|
|
@@ -19216,7 +19676,7 @@ function createConnectionHandler(options) {
|
|
|
19216
19676
|
}
|
|
19217
19677
|
|
|
19218
19678
|
// src/server/message-dispatcher.ts
|
|
19219
|
-
import
|
|
19679
|
+
import path23 from "node:path";
|
|
19220
19680
|
function createMessageDispatcher(opts) {
|
|
19221
19681
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
19222
19682
|
function makeWorklistContext() {
|
|
@@ -19237,7 +19697,7 @@ function createMessageDispatcher(opts) {
|
|
|
19237
19697
|
skillLoader: deps2.skillLoader,
|
|
19238
19698
|
skillInstaller: deps2.skillInstaller,
|
|
19239
19699
|
projectRoot,
|
|
19240
|
-
projectSkillsDir:
|
|
19700
|
+
projectSkillsDir: path23.join(projectRoot, ".wrongstack", "skills"),
|
|
19241
19701
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
19242
19702
|
};
|
|
19243
19703
|
}
|
|
@@ -19489,7 +19949,7 @@ function createMessageDispatcher(opts) {
|
|
|
19489
19949
|
|
|
19490
19950
|
// src/server/pre-context-services.ts
|
|
19491
19951
|
import { createRequire as createRequire3 } from "node:module";
|
|
19492
|
-
import * as
|
|
19952
|
+
import * as path26 from "node:path";
|
|
19493
19953
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
19494
19954
|
import {
|
|
19495
19955
|
getSharedProjectMailbox as getSharedProjectMailbox3,
|
|
@@ -19543,8 +20003,8 @@ import { configureDangerBypass, configureExecPolicy } from "@wrongstack/tools";
|
|
|
19543
20003
|
import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
|
|
19544
20004
|
|
|
19545
20005
|
// src/server/model-auto-discovery.ts
|
|
19546
|
-
import * as
|
|
19547
|
-
import * as
|
|
20006
|
+
import * as fs19 from "node:fs/promises";
|
|
20007
|
+
import * as path24 from "node:path";
|
|
19548
20008
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
19549
20009
|
function isOverlayRegistry(value) {
|
|
19550
20010
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -19570,7 +20030,7 @@ function eligibleProviders(config) {
|
|
|
19570
20030
|
}
|
|
19571
20031
|
async function readCache(file) {
|
|
19572
20032
|
try {
|
|
19573
|
-
return JSON.parse(await
|
|
20033
|
+
return JSON.parse(await fs19.readFile(file, "utf8"));
|
|
19574
20034
|
} catch {
|
|
19575
20035
|
return {};
|
|
19576
20036
|
}
|
|
@@ -19580,7 +20040,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19580
20040
|
if (!isOverlayRegistry(registry)) return;
|
|
19581
20041
|
const targets = eligibleProviders(opts.config);
|
|
19582
20042
|
if (targets.length === 0) return;
|
|
19583
|
-
const cacheFile =
|
|
20043
|
+
const cacheFile = path24.join(opts.cacheDir, "discovered-models-cache.json");
|
|
19584
20044
|
const cache2 = await readCache(cacheFile);
|
|
19585
20045
|
let cacheDirty = false;
|
|
19586
20046
|
await Promise.all(
|
|
@@ -19617,8 +20077,8 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19617
20077
|
);
|
|
19618
20078
|
if (cacheDirty) {
|
|
19619
20079
|
try {
|
|
19620
|
-
await
|
|
19621
|
-
await
|
|
20080
|
+
await fs19.mkdir(path24.dirname(cacheFile), { recursive: true });
|
|
20081
|
+
await fs19.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
19622
20082
|
} catch {
|
|
19623
20083
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
19624
20084
|
}
|
|
@@ -19714,7 +20174,7 @@ function resolveSetupProvider(opts) {
|
|
|
19714
20174
|
}
|
|
19715
20175
|
|
|
19716
20176
|
// src/server/standalone-session-identity.ts
|
|
19717
|
-
import * as
|
|
20177
|
+
import * as path25 from "node:path";
|
|
19718
20178
|
import {
|
|
19719
20179
|
AgentStatusTracker,
|
|
19720
20180
|
FleetNotifier,
|
|
@@ -19733,7 +20193,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
19733
20193
|
let activeTarget = {
|
|
19734
20194
|
projectSlug: paths.projectSlug,
|
|
19735
20195
|
projectRoot: paths.projectRoot,
|
|
19736
|
-
projectName:
|
|
20196
|
+
projectName: path25.basename(paths.projectRoot),
|
|
19737
20197
|
workingDir: opts.workingDir
|
|
19738
20198
|
};
|
|
19739
20199
|
let pendingClaim;
|
|
@@ -19964,7 +20424,7 @@ async function createPreContextServices(input) {
|
|
|
19964
20424
|
await discoverAndMergeWebuiProviders({
|
|
19965
20425
|
config,
|
|
19966
20426
|
registry: modelsRegistry,
|
|
19967
|
-
cacheDir:
|
|
20427
|
+
cacheDir: path26.dirname(wpaths.modelsCache),
|
|
19968
20428
|
logger
|
|
19969
20429
|
});
|
|
19970
20430
|
} catch (err) {
|
|
@@ -20016,7 +20476,7 @@ async function createPreContextServices(input) {
|
|
|
20016
20476
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
20017
20477
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
20018
20478
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
20019
|
-
|
|
20479
|
+
path26.join(wpaths.projectDir, "mcp-auth.json"),
|
|
20020
20480
|
vault
|
|
20021
20481
|
);
|
|
20022
20482
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -20122,7 +20582,7 @@ async function createPreContextServices(input) {
|
|
|
20122
20582
|
};
|
|
20123
20583
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
20124
20584
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
20125
|
-
manifestPath:
|
|
20585
|
+
manifestPath: path26.join(wpaths.configDir, "installed-skills.json"),
|
|
20126
20586
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
20127
20587
|
globalSkillsDir: wpaths.globalSkills,
|
|
20128
20588
|
projectHash: wpaths.projectHash,
|
|
@@ -20132,8 +20592,8 @@ async function createPreContextServices(input) {
|
|
|
20132
20592
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
20133
20593
|
try {
|
|
20134
20594
|
const req = createRequire3(import.meta.url);
|
|
20135
|
-
return
|
|
20136
|
-
|
|
20595
|
+
return path26.join(
|
|
20596
|
+
path26.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
20137
20597
|
"data",
|
|
20138
20598
|
"prompts"
|
|
20139
20599
|
);
|
|
@@ -20237,7 +20697,7 @@ async function createPreContextServices(input) {
|
|
|
20237
20697
|
}
|
|
20238
20698
|
|
|
20239
20699
|
// src/server/routes.ts
|
|
20240
|
-
import
|
|
20700
|
+
import path27 from "node:path";
|
|
20241
20701
|
import { makeProviderFromConfig as makeProviderFromConfig2, withCatalogCapabilities } from "@wrongstack/providers";
|
|
20242
20702
|
|
|
20243
20703
|
// src/server/mode-handlers.ts
|
|
@@ -20529,7 +20989,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20529
20989
|
};
|
|
20530
20990
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
20531
20991
|
getProjectRoot: state.getProjectRoot,
|
|
20532
|
-
getGlobalRoot: () =>
|
|
20992
|
+
getGlobalRoot: () => path27.dirname(deps2.globalConfigPath),
|
|
20533
20993
|
events: deps2.events
|
|
20534
20994
|
});
|
|
20535
20995
|
const mcpRoutes = {
|
|
@@ -20602,7 +21062,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20602
21062
|
}
|
|
20603
21063
|
|
|
20604
21064
|
// src/server/server-runtime.ts
|
|
20605
|
-
import * as
|
|
21065
|
+
import * as path28 from "node:path";
|
|
20606
21066
|
import { createRequire as createRequire4 } from "node:module";
|
|
20607
21067
|
import { fileURLToPath } from "node:url";
|
|
20608
21068
|
import { WebSocketServer } from "ws";
|
|
@@ -20663,7 +21123,7 @@ function createSessionStartPayload(g) {
|
|
|
20663
21123
|
inputCost,
|
|
20664
21124
|
outputCost,
|
|
20665
21125
|
cacheReadCost,
|
|
20666
|
-
projectName:
|
|
21126
|
+
projectName: path28.basename(projectRoot) || projectRoot,
|
|
20667
21127
|
projectRoot,
|
|
20668
21128
|
cwd: g.getWorkingDir(),
|
|
20669
21129
|
mode: g.getModeId(),
|
|
@@ -20751,13 +21211,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
20751
21211
|
};
|
|
20752
21212
|
}
|
|
20753
21213
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
20754
|
-
if (explicitDistDir) return
|
|
21214
|
+
if (explicitDistDir) return path28.resolve(explicitDistDir);
|
|
20755
21215
|
try {
|
|
20756
21216
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
20757
21217
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
20758
|
-
return
|
|
21218
|
+
return path28.dirname(serverEntry);
|
|
20759
21219
|
} catch {
|
|
20760
|
-
return
|
|
21220
|
+
return path28.resolve(path28.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
20761
21221
|
}
|
|
20762
21222
|
}
|
|
20763
21223
|
function startHttpServer(opts) {
|
|
@@ -20971,6 +21431,7 @@ async function startWebUI(opts = {}) {
|
|
|
20971
21431
|
worktreeHandler,
|
|
20972
21432
|
terminalHandler,
|
|
20973
21433
|
collabHandler,
|
|
21434
|
+
disposeRealtimeHandlers,
|
|
20974
21435
|
updateAutoCompactionMaxContext
|
|
20975
21436
|
} = agentServices;
|
|
20976
21437
|
if (typeof context.meta["yolo"] === "boolean") {
|
|
@@ -21149,21 +21610,21 @@ async function startWebUI(opts = {}) {
|
|
|
21149
21610
|
});
|
|
21150
21611
|
}
|
|
21151
21612
|
async function touchProjectEntry(root, workDir) {
|
|
21152
|
-
const resolved =
|
|
21613
|
+
const resolved = path29.resolve(root);
|
|
21153
21614
|
const manifest = await loadManifest(globalConfigPath);
|
|
21154
21615
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21155
|
-
const existing = manifest.projects.find((p) =>
|
|
21616
|
+
const existing = manifest.projects.find((p) => path29.resolve(p.root) === resolved);
|
|
21156
21617
|
if (existing) {
|
|
21157
21618
|
existing.lastSeen = now;
|
|
21158
|
-
if (workDir) existing.lastWorkingDir =
|
|
21619
|
+
if (workDir) existing.lastWorkingDir = path29.resolve(workDir);
|
|
21159
21620
|
} else {
|
|
21160
21621
|
manifest.projects.push({
|
|
21161
|
-
name:
|
|
21622
|
+
name: path29.basename(resolved),
|
|
21162
21623
|
root: resolved,
|
|
21163
21624
|
slug: generateProjectSlug(resolved),
|
|
21164
21625
|
createdAt: now,
|
|
21165
21626
|
lastSeen: now,
|
|
21166
|
-
lastWorkingDir: workDir ?
|
|
21627
|
+
lastWorkingDir: workDir ? path29.resolve(workDir) : void 0
|
|
21167
21628
|
});
|
|
21168
21629
|
}
|
|
21169
21630
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -21340,7 +21801,20 @@ async function startWebUI(opts = {}) {
|
|
|
21340
21801
|
);
|
|
21341
21802
|
credentialWatcherClose = credentialWatcher.close;
|
|
21342
21803
|
}
|
|
21343
|
-
const stopHeapWatchdog =
|
|
21804
|
+
const stopHeapWatchdog = startSharedHeapWatchdog({
|
|
21805
|
+
collectStats: () => ({
|
|
21806
|
+
surface: opts.surface ?? "webui",
|
|
21807
|
+
sessionId: context.session.id,
|
|
21808
|
+
messages: context.state.messages.length,
|
|
21809
|
+
messageEstimatedTokens: context.state.messages.reduce(
|
|
21810
|
+
(sum, message) => sum + (message._estTokens ?? 0),
|
|
21811
|
+
0
|
|
21812
|
+
),
|
|
21813
|
+
webClients: clients.size,
|
|
21814
|
+
pendingConfirms: pendingConfirms.size,
|
|
21815
|
+
runActive: runLockControl.get() !== null
|
|
21816
|
+
})
|
|
21817
|
+
});
|
|
21344
21818
|
const routes = buildRoutes(state, deps2, cb);
|
|
21345
21819
|
const handleMessage = createMessageDispatcher({
|
|
21346
21820
|
state,
|
|
@@ -21418,6 +21892,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21418
21892
|
await todosCheckpoint.detach();
|
|
21419
21893
|
await stopHeapWatchdog();
|
|
21420
21894
|
credentialWatcherClose?.();
|
|
21895
|
+
disposeRealtimeHandlers();
|
|
21421
21896
|
brainMonitor.stop();
|
|
21422
21897
|
await agentServices.brainLedger?.stop().catch(() => {
|
|
21423
21898
|
});
|
|
@@ -21441,7 +21916,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21441
21916
|
await memoryStore.dispose().catch(
|
|
21442
21917
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage13(err)}`)
|
|
21443
21918
|
);
|
|
21444
|
-
await unregisterInstance(process.pid,
|
|
21919
|
+
await unregisterInstance(process.pid, path29.dirname(globalConfigPath));
|
|
21445
21920
|
}
|
|
21446
21921
|
});
|
|
21447
21922
|
}
|
|
@@ -21534,7 +22009,7 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
21534
22009
|
console.error(err instanceof Error ? err.message : String(err));
|
|
21535
22010
|
process.exit(1);
|
|
21536
22011
|
}
|
|
21537
|
-
const
|
|
22012
|
+
const open2 = argv.includes("--open") || argv.includes("-o") || process.env["WEBUI_OPEN"] === "1";
|
|
21538
22013
|
const requireToken = argv.includes("--require-token") || envFlag2("WEBUI_REQUIRE_TOKEN");
|
|
21539
22014
|
console.log(`[WebUI] Starting standalone server on ${wsHost} (http:${httpPort})...`);
|
|
21540
22015
|
startWebUI({
|
|
@@ -21544,7 +22019,7 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
21544
22019
|
publicUrl,
|
|
21545
22020
|
publicWsUrl,
|
|
21546
22021
|
requireToken,
|
|
21547
|
-
open,
|
|
22022
|
+
open: open2,
|
|
21548
22023
|
distDir
|
|
21549
22024
|
}).catch((err) => {
|
|
21550
22025
|
console.error(JSON.stringify({
|