@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/index.js
CHANGED
|
@@ -134,85 +134,85 @@ var ENUM_PREF_KEYS = {
|
|
|
134
134
|
autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
|
|
135
135
|
fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
|
|
136
136
|
};
|
|
137
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
137
|
+
function validateModelRuntimeValue(modelRuntime, path35) {
|
|
138
138
|
const reasoning = modelRuntime["reasoning"];
|
|
139
139
|
if (reasoning !== void 0) {
|
|
140
|
-
if (!isRecord(reasoning)) return `${
|
|
140
|
+
if (!isRecord(reasoning)) return `${path35}.reasoning must be an object when provided`;
|
|
141
141
|
const mode = reasoning["mode"];
|
|
142
142
|
const effort = reasoning["effort"];
|
|
143
143
|
const preserve = reasoning["preserve"];
|
|
144
144
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
145
|
-
return `${
|
|
145
|
+
return `${path35}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
146
146
|
}
|
|
147
147
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
148
|
-
return `${
|
|
148
|
+
return `${path35}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
149
149
|
}
|
|
150
150
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
151
|
-
return `${
|
|
151
|
+
return `${path35}.reasoning.preserve must be a boolean when provided`;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
const cache2 = modelRuntime["cache"];
|
|
155
155
|
if (cache2 !== void 0) {
|
|
156
|
-
if (!isRecord(cache2)) return `${
|
|
156
|
+
if (!isRecord(cache2)) return `${path35}.cache must be an object when provided`;
|
|
157
157
|
const ttl = cache2["ttl"];
|
|
158
158
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
159
|
-
return `${
|
|
159
|
+
return `${path35}.cache.ttl must be one of: 5m, 1h`;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
const parameters = modelRuntime["parameters"];
|
|
163
163
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
164
|
-
return `${
|
|
164
|
+
return `${path35}.parameters must be an object when provided`;
|
|
165
165
|
}
|
|
166
166
|
return null;
|
|
167
167
|
}
|
|
168
|
-
function validateModelBlackoutRule(rule,
|
|
168
|
+
function validateModelBlackoutRule(rule, path35) {
|
|
169
169
|
const id = rule["id"];
|
|
170
170
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
171
|
-
return `${
|
|
171
|
+
return `${path35}.id must be a non-empty string`;
|
|
172
172
|
}
|
|
173
173
|
const start = rule["start"];
|
|
174
174
|
if (typeof start !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(start)) {
|
|
175
|
-
return `${
|
|
175
|
+
return `${path35}.start must be a string in HH:mm (00:00-23:59) format`;
|
|
176
176
|
}
|
|
177
177
|
const end = rule["end"];
|
|
178
178
|
if (typeof end !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(end)) {
|
|
179
|
-
return `${
|
|
179
|
+
return `${path35}.end must be a string in HH:mm (00:00-23:59) format`;
|
|
180
180
|
}
|
|
181
181
|
if (rule["enabled"] !== void 0 && typeof rule["enabled"] !== "boolean") {
|
|
182
|
-
return `${
|
|
182
|
+
return `${path35}.enabled must be a boolean when provided`;
|
|
183
183
|
}
|
|
184
184
|
if (rule["provider"] !== void 0 && typeof rule["provider"] !== "string") {
|
|
185
|
-
return `${
|
|
185
|
+
return `${path35}.provider must be a string when provided`;
|
|
186
186
|
}
|
|
187
187
|
if (rule["model"] !== void 0 && typeof rule["model"] !== "string") {
|
|
188
|
-
return `${
|
|
188
|
+
return `${path35}.model must be a string when provided`;
|
|
189
189
|
}
|
|
190
190
|
if (rule["days"] !== void 0) {
|
|
191
|
-
if (!Array.isArray(rule["days"])) return `${
|
|
191
|
+
if (!Array.isArray(rule["days"])) return `${path35}.days must be an array when provided`;
|
|
192
192
|
const seen = /* @__PURE__ */ new Set();
|
|
193
193
|
for (const d of rule["days"]) {
|
|
194
194
|
if (typeof d !== "number" || !Number.isInteger(d) || d < 0 || d > 6) {
|
|
195
|
-
return `${
|
|
195
|
+
return `${path35}.days elements must be integers 0-6 when provided`;
|
|
196
196
|
}
|
|
197
|
-
if (seen.has(d)) return `${
|
|
197
|
+
if (seen.has(d)) return `${path35}.days contains duplicate day: ${d}`;
|
|
198
198
|
seen.add(d);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
if (rule["timezone"] !== void 0) {
|
|
202
202
|
if (typeof rule["timezone"] !== "string") {
|
|
203
|
-
return `${
|
|
203
|
+
return `${path35}.timezone must be a string when provided`;
|
|
204
204
|
}
|
|
205
205
|
try {
|
|
206
206
|
Intl.DateTimeFormat(void 0, { timeZone: rule["timezone"] });
|
|
207
207
|
} catch {
|
|
208
|
-
return `${
|
|
208
|
+
return `${path35}.timezone is not a valid IANA timezone (e.g. "America/New_York")`;
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
if (rule["label"] !== void 0 && typeof rule["label"] !== "string") {
|
|
212
|
-
return `${
|
|
212
|
+
return `${path35}.label must be a string when provided`;
|
|
213
213
|
}
|
|
214
214
|
if (rule["mode"] !== void 0 && rule["mode"] !== "blackout" && rule["mode"] !== "allow_only") {
|
|
215
|
-
return `${
|
|
215
|
+
return `${path35}.mode must be 'blackout' or 'allow_only' when provided`;
|
|
216
216
|
}
|
|
217
217
|
return null;
|
|
218
218
|
}
|
|
@@ -385,6 +385,37 @@ function validateMailboxMessagesPayload(payload) {
|
|
|
385
385
|
}
|
|
386
386
|
};
|
|
387
387
|
}
|
|
388
|
+
var MAILBOX_ACTIONS = /* @__PURE__ */ new Set(["mark-read", "acknowledge", "reopen", "soft-delete"]);
|
|
389
|
+
function validateMailboxActionPayload(payload) {
|
|
390
|
+
if (!isRecord2(payload)) {
|
|
391
|
+
return { ok: false, message: "mailbox.action payload must be an object" };
|
|
392
|
+
}
|
|
393
|
+
const requestId = payload["requestId"];
|
|
394
|
+
const mailId = payload["mailId"];
|
|
395
|
+
const action = payload["action"];
|
|
396
|
+
const readerId = payload["readerId"];
|
|
397
|
+
if (typeof requestId !== "string" || requestId.trim().length === 0) {
|
|
398
|
+
return { ok: false, message: "mailbox.action payload.requestId must be a non-empty string" };
|
|
399
|
+
}
|
|
400
|
+
if (typeof mailId !== "string" || mailId.trim().length === 0) {
|
|
401
|
+
return { ok: false, message: "mailbox.action payload.mailId must be a non-empty string" };
|
|
402
|
+
}
|
|
403
|
+
if (typeof action !== "string" || !MAILBOX_ACTIONS.has(action)) {
|
|
404
|
+
return { ok: false, message: "mailbox.action payload.action must be a supported action" };
|
|
405
|
+
}
|
|
406
|
+
if (typeof readerId !== "string" || readerId.trim().length === 0) {
|
|
407
|
+
return { ok: false, message: "mailbox.action payload.readerId must be a non-empty string" };
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
ok: true,
|
|
411
|
+
value: {
|
|
412
|
+
requestId: requestId.trim(),
|
|
413
|
+
mailId: mailId.trim(),
|
|
414
|
+
action,
|
|
415
|
+
readerId: readerId.trim()
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
388
419
|
var MAILBOX_SEND_TYPES = /* @__PURE__ */ new Set([
|
|
389
420
|
"note",
|
|
390
421
|
"ask",
|
|
@@ -401,6 +432,7 @@ function validateMailboxSendPayload(payload) {
|
|
|
401
432
|
return { ok: false, message: "mailbox.send payload must be an object" };
|
|
402
433
|
}
|
|
403
434
|
const requestId = payload["requestId"];
|
|
435
|
+
const rawFrom = payload["from"];
|
|
404
436
|
const rawTo = payload["to"];
|
|
405
437
|
const rawType = payload["type"];
|
|
406
438
|
const rawAudience = payload["audience"];
|
|
@@ -411,6 +443,9 @@ function validateMailboxSendPayload(payload) {
|
|
|
411
443
|
if (typeof requestId !== "string" || requestId.trim().length === 0) {
|
|
412
444
|
return { ok: false, message: "mailbox.send payload.requestId must be a non-empty string" };
|
|
413
445
|
}
|
|
446
|
+
if (rawFrom !== void 0 && (typeof rawFrom !== "string" || rawFrom.trim().length === 0)) {
|
|
447
|
+
return { ok: false, message: "mailbox.send payload.from must be a non-empty string when provided" };
|
|
448
|
+
}
|
|
414
449
|
if (typeof rawTo !== "string" || rawTo.trim().length === 0) {
|
|
415
450
|
return { ok: false, message: "mailbox.send payload.to must be a non-empty string" };
|
|
416
451
|
}
|
|
@@ -447,6 +482,7 @@ function validateMailboxSendPayload(payload) {
|
|
|
447
482
|
ok: true,
|
|
448
483
|
value: {
|
|
449
484
|
requestId: requestId.trim(),
|
|
485
|
+
...rawFrom !== void 0 ? { from: rawFrom.trim() } : {},
|
|
450
486
|
to,
|
|
451
487
|
type,
|
|
452
488
|
audience: rawAudience,
|
|
@@ -760,8 +796,8 @@ function validateShellOpenPayload(payload) {
|
|
|
760
796
|
if (!isRecord2(payload)) {
|
|
761
797
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
762
798
|
}
|
|
763
|
-
const
|
|
764
|
-
if (typeof
|
|
799
|
+
const path35 = payload["path"];
|
|
800
|
+
if (typeof path35 !== "string" || path35.trim().length === 0) {
|
|
765
801
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
766
802
|
}
|
|
767
803
|
const target = payload["target"];
|
|
@@ -774,7 +810,7 @@ function validateShellOpenPayload(payload) {
|
|
|
774
810
|
return {
|
|
775
811
|
ok: true,
|
|
776
812
|
value: {
|
|
777
|
-
path:
|
|
813
|
+
path: path35,
|
|
778
814
|
...target !== void 0 ? { target } : {}
|
|
779
815
|
}
|
|
780
816
|
};
|
|
@@ -783,14 +819,14 @@ function validateGitDiffPayload(payload) {
|
|
|
783
819
|
if (!isRecord2(payload)) {
|
|
784
820
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
785
821
|
}
|
|
786
|
-
const
|
|
787
|
-
if (
|
|
822
|
+
const path35 = payload["path"];
|
|
823
|
+
if (path35 === void 0 || path35 === null) {
|
|
788
824
|
return { ok: true, value: { path: "" } };
|
|
789
825
|
}
|
|
790
|
-
if (typeof
|
|
826
|
+
if (typeof path35 !== "string") {
|
|
791
827
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
792
828
|
}
|
|
793
|
-
return { ok: true, value: { path:
|
|
829
|
+
return { ok: true, value: { path: path35 } };
|
|
794
830
|
}
|
|
795
831
|
function validateProjectsAddPayload(payload) {
|
|
796
832
|
if (!isRecord2(payload)) {
|
|
@@ -1575,6 +1611,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1575
1611
|
for (const off of this.offs) off();
|
|
1576
1612
|
this.offs.length = 0;
|
|
1577
1613
|
this.stopBroadcast();
|
|
1614
|
+
this.clients.clear();
|
|
1578
1615
|
}
|
|
1579
1616
|
// ── Inbound client messages ────────────────────────────────────────────
|
|
1580
1617
|
/**
|
|
@@ -1634,9 +1671,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1634
1671
|
if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
|
|
1635
1672
|
this.send(
|
|
1636
1673
|
ws,
|
|
1637
|
-
this.errorMessage(
|
|
1638
|
-
`collab.join sessionId mismatch (active: ${activeSessionId})`
|
|
1639
|
-
)
|
|
1674
|
+
this.errorMessage(`collab.join sessionId mismatch (active: ${activeSessionId})`)
|
|
1640
1675
|
);
|
|
1641
1676
|
return;
|
|
1642
1677
|
}
|
|
@@ -1647,26 +1682,19 @@ var CollaborationWebSocketHandler = class {
|
|
|
1647
1682
|
if (role === "controller" && !this.bus) {
|
|
1648
1683
|
this.send(
|
|
1649
1684
|
ws,
|
|
1650
|
-
this.errorMessage(
|
|
1651
|
-
`role 'controller' is not available: server has no CollaborationBus`
|
|
1652
|
-
)
|
|
1685
|
+
this.errorMessage(`role 'controller' is not available: server has no CollaborationBus`)
|
|
1653
1686
|
);
|
|
1654
1687
|
return;
|
|
1655
1688
|
}
|
|
1656
1689
|
if (role === "annotator" && !this.annotations) {
|
|
1657
1690
|
this.send(
|
|
1658
1691
|
ws,
|
|
1659
|
-
this.errorMessage(
|
|
1660
|
-
`role 'annotator' is not available: server has no annotations store`
|
|
1661
|
-
)
|
|
1692
|
+
this.errorMessage(`role 'annotator' is not available: server has no annotations store`)
|
|
1662
1693
|
);
|
|
1663
1694
|
return;
|
|
1664
1695
|
}
|
|
1665
1696
|
if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
|
|
1666
|
-
this.send(
|
|
1667
|
-
ws,
|
|
1668
|
-
this.errorMessage(`role '${role}' requires explicit server authorization`)
|
|
1669
|
-
);
|
|
1697
|
+
this.send(ws, this.errorMessage(`role '${role}' requires explicit server authorization`));
|
|
1670
1698
|
return;
|
|
1671
1699
|
}
|
|
1672
1700
|
const participant = {
|
|
@@ -1695,14 +1723,10 @@ var CollaborationWebSocketHandler = class {
|
|
|
1695
1723
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
1696
1724
|
if (this.reader) {
|
|
1697
1725
|
this.replayHistory(ws, sessionId).catch((err) => {
|
|
1698
|
-
this.logger.debug?.(
|
|
1699
|
-
`collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`
|
|
1700
|
-
);
|
|
1726
|
+
this.logger.debug?.(`collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`);
|
|
1701
1727
|
});
|
|
1702
1728
|
}
|
|
1703
|
-
this.logger.debug?.(
|
|
1704
|
-
`collab: participant ${participant.participantId} joined ${sessionId}`
|
|
1705
|
-
);
|
|
1729
|
+
this.logger.debug?.(`collab: participant ${participant.participantId} joined ${sessionId}`);
|
|
1706
1730
|
}
|
|
1707
1731
|
leave(ws) {
|
|
1708
1732
|
this.handleDisconnect(ws);
|
|
@@ -1773,18 +1797,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
1773
1797
|
}
|
|
1774
1798
|
const payload = raw;
|
|
1775
1799
|
if (!payload?.sessionId || typeof payload.atEventIndex !== "number" || typeof payload.text !== "string") {
|
|
1776
|
-
this.send(
|
|
1777
|
-
ws,
|
|
1778
|
-
this.errorMessage("annotate requires { sessionId, atEventIndex, text }")
|
|
1779
|
-
);
|
|
1800
|
+
this.send(ws, this.errorMessage("annotate requires { sessionId, atEventIndex, text }"));
|
|
1780
1801
|
return;
|
|
1781
1802
|
}
|
|
1782
1803
|
if (payload.sessionId !== participant.sessionId) {
|
|
1783
1804
|
this.send(
|
|
1784
1805
|
ws,
|
|
1785
|
-
this.errorMessage(
|
|
1786
|
-
`annotate sessionId mismatch (joined: ${participant.sessionId})`
|
|
1787
|
-
)
|
|
1806
|
+
this.errorMessage(`annotate sessionId mismatch (joined: ${participant.sessionId})`)
|
|
1788
1807
|
);
|
|
1789
1808
|
return;
|
|
1790
1809
|
}
|
|
@@ -1811,12 +1830,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1811
1830
|
}
|
|
1812
1831
|
});
|
|
1813
1832
|
} catch (err) {
|
|
1814
|
-
this.send(
|
|
1815
|
-
ws,
|
|
1816
|
-
this.errorMessage(
|
|
1817
|
-
`annotation rejected: ${toErrorMessage(err)}`
|
|
1818
|
-
)
|
|
1819
|
-
);
|
|
1833
|
+
this.send(ws, this.errorMessage(`annotation rejected: ${toErrorMessage(err)}`));
|
|
1820
1834
|
}
|
|
1821
1835
|
}
|
|
1822
1836
|
async handleResolve(ws, raw) {
|
|
@@ -1832,26 +1846,19 @@ var CollaborationWebSocketHandler = class {
|
|
|
1832
1846
|
if (participant.role !== "annotator") {
|
|
1833
1847
|
this.send(
|
|
1834
1848
|
ws,
|
|
1835
|
-
this.errorMessage(
|
|
1836
|
-
`resolve requires the 'annotator' role (current: '${participant.role}')`
|
|
1837
|
-
)
|
|
1849
|
+
this.errorMessage(`resolve requires the 'annotator' role (current: '${participant.role}')`)
|
|
1838
1850
|
);
|
|
1839
1851
|
return;
|
|
1840
1852
|
}
|
|
1841
1853
|
const payload = raw;
|
|
1842
1854
|
if (!payload?.sessionId || !payload.annotationId) {
|
|
1843
|
-
this.send(
|
|
1844
|
-
ws,
|
|
1845
|
-
this.errorMessage("resolve requires { sessionId, annotationId }")
|
|
1846
|
-
);
|
|
1855
|
+
this.send(ws, this.errorMessage("resolve requires { sessionId, annotationId }"));
|
|
1847
1856
|
return;
|
|
1848
1857
|
}
|
|
1849
1858
|
if (payload.sessionId !== participant.sessionId) {
|
|
1850
1859
|
this.send(
|
|
1851
1860
|
ws,
|
|
1852
|
-
this.errorMessage(
|
|
1853
|
-
`resolve sessionId mismatch (joined: ${participant.sessionId})`
|
|
1854
|
-
)
|
|
1861
|
+
this.errorMessage(`resolve sessionId mismatch (joined: ${participant.sessionId})`)
|
|
1855
1862
|
);
|
|
1856
1863
|
return;
|
|
1857
1864
|
}
|
|
@@ -1862,10 +1869,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1862
1869
|
resolvedBy: participant.participantId
|
|
1863
1870
|
});
|
|
1864
1871
|
if (!updated) {
|
|
1865
|
-
this.send(
|
|
1866
|
-
ws,
|
|
1867
|
-
this.errorMessage(`annotation not found: ${payload.annotationId}`)
|
|
1868
|
-
);
|
|
1872
|
+
this.send(ws, this.errorMessage(`annotation not found: ${payload.annotationId}`));
|
|
1869
1873
|
return;
|
|
1870
1874
|
}
|
|
1871
1875
|
this.broadcast(payload.sessionId, {
|
|
@@ -1878,12 +1882,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1878
1882
|
}
|
|
1879
1883
|
});
|
|
1880
1884
|
} catch (err) {
|
|
1881
|
-
this.send(
|
|
1882
|
-
ws,
|
|
1883
|
-
this.errorMessage(
|
|
1884
|
-
`resolve failed: ${toErrorMessage(err)}`
|
|
1885
|
-
)
|
|
1886
|
-
);
|
|
1885
|
+
this.send(ws, this.errorMessage(`resolve failed: ${toErrorMessage(err)}`));
|
|
1887
1886
|
}
|
|
1888
1887
|
}
|
|
1889
1888
|
// ── Event subscription (live mirror) ───────────────────────────────────
|
|
@@ -1954,9 +1953,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1954
1953
|
seen++;
|
|
1955
1954
|
}
|
|
1956
1955
|
} catch (err) {
|
|
1957
|
-
this.logger.debug?.(
|
|
1958
|
-
`collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`
|
|
1959
|
-
);
|
|
1956
|
+
this.logger.debug?.(`collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`);
|
|
1960
1957
|
return;
|
|
1961
1958
|
}
|
|
1962
1959
|
const tail2 = seen <= REPLAY_LIMIT ? ring.slice(0, seen) : (
|
|
@@ -2038,9 +2035,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2038
2035
|
try {
|
|
2039
2036
|
sendSerialized(p.ws, data);
|
|
2040
2037
|
} catch (err) {
|
|
2041
|
-
this.logger.debug?.(
|
|
2042
|
-
`collab broadcast failed: ${toErrorMessage(err)}`
|
|
2043
|
-
);
|
|
2038
|
+
this.logger.debug?.(`collab broadcast failed: ${toErrorMessage(err)}`);
|
|
2044
2039
|
}
|
|
2045
2040
|
}
|
|
2046
2041
|
}
|
|
@@ -2067,9 +2062,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2067
2062
|
if (participant.role !== "controller") {
|
|
2068
2063
|
this.send(
|
|
2069
2064
|
ws,
|
|
2070
|
-
this.errorMessage(
|
|
2071
|
-
`pause requires the 'controller' role (current: '${participant.role}')`
|
|
2072
|
-
)
|
|
2065
|
+
this.errorMessage(`pause requires the 'controller' role (current: '${participant.role}')`)
|
|
2073
2066
|
);
|
|
2074
2067
|
return;
|
|
2075
2068
|
}
|
|
@@ -2114,9 +2107,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2114
2107
|
if (participant.role !== "controller") {
|
|
2115
2108
|
this.send(
|
|
2116
2109
|
ws,
|
|
2117
|
-
this.errorMessage(
|
|
2118
|
-
`resume requires the 'controller' role (current: '${participant.role}')`
|
|
2119
|
-
)
|
|
2110
|
+
this.errorMessage(`resume requires the 'controller' role (current: '${participant.role}')`)
|
|
2120
2111
|
);
|
|
2121
2112
|
return;
|
|
2122
2113
|
}
|
|
@@ -2214,9 +2205,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2214
2205
|
if (payload.sessionId !== participant.sessionId) {
|
|
2215
2206
|
this.send(
|
|
2216
2207
|
ws,
|
|
2217
|
-
this.errorMessage(
|
|
2218
|
-
`inject_tool sessionId mismatch (joined: ${participant.sessionId})`
|
|
2219
|
-
)
|
|
2208
|
+
this.errorMessage(`inject_tool sessionId mismatch (joined: ${participant.sessionId})`)
|
|
2220
2209
|
);
|
|
2221
2210
|
return;
|
|
2222
2211
|
}
|
|
@@ -2230,9 +2219,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2230
2219
|
if (!queued) {
|
|
2231
2220
|
this.send(
|
|
2232
2221
|
ws,
|
|
2233
|
-
this.errorMessage(
|
|
2234
|
-
`an injection for toolUseId ${payload.toolUseId} is already queued`
|
|
2235
|
-
)
|
|
2222
|
+
this.errorMessage(`an injection for toolUseId ${payload.toolUseId} is already queued`)
|
|
2236
2223
|
);
|
|
2237
2224
|
return;
|
|
2238
2225
|
}
|
|
@@ -4376,8 +4363,8 @@ function jsonByteLength(value) {
|
|
|
4376
4363
|
return MAX_PAYLOAD_BYTES + 1;
|
|
4377
4364
|
}
|
|
4378
4365
|
}
|
|
4379
|
-
function error(errors,
|
|
4380
|
-
errors.push({ path:
|
|
4366
|
+
function error(errors, path35, code, message) {
|
|
4367
|
+
errors.push({ path: path35, code, message });
|
|
4381
4368
|
}
|
|
4382
4369
|
function isMessageRole(value) {
|
|
4383
4370
|
return value === "user" || value === "assistant" || value === "system";
|
|
@@ -4385,25 +4372,25 @@ function isMessageRole(value) {
|
|
|
4385
4372
|
function isPlainJsonObject(value) {
|
|
4386
4373
|
return isRecord3(value);
|
|
4387
4374
|
}
|
|
4388
|
-
function validateCacheControl(value,
|
|
4375
|
+
function validateCacheControl(value, path35, errors) {
|
|
4389
4376
|
if (value === void 0) return void 0;
|
|
4390
4377
|
if (!isRecord3(value) || value["type"] !== "ephemeral") {
|
|
4391
|
-
error(errors,
|
|
4378
|
+
error(errors, path35, "INVALID_CACHE_CONTROL", 'cache_control must be { type: "ephemeral" }.');
|
|
4392
4379
|
return void 0;
|
|
4393
4380
|
}
|
|
4394
4381
|
return { type: "ephemeral" };
|
|
4395
4382
|
}
|
|
4396
|
-
function validateProviderMeta(value,
|
|
4383
|
+
function validateProviderMeta(value, path35, errors) {
|
|
4397
4384
|
if (value === void 0) return void 0;
|
|
4398
4385
|
if (!isPlainJsonObject(value)) {
|
|
4399
|
-
error(errors,
|
|
4386
|
+
error(errors, path35, "INVALID_PROVIDER_META", "providerMeta must be a JSON object.");
|
|
4400
4387
|
return void 0;
|
|
4401
4388
|
}
|
|
4402
4389
|
return value;
|
|
4403
4390
|
}
|
|
4404
|
-
function validateBlock(value,
|
|
4391
|
+
function validateBlock(value, path35, errors) {
|
|
4405
4392
|
if (!isRecord3(value)) {
|
|
4406
|
-
error(errors,
|
|
4393
|
+
error(errors, path35, "INVALID_BLOCK", "Content block must be an object.");
|
|
4407
4394
|
return void 0;
|
|
4408
4395
|
}
|
|
4409
4396
|
const type = value["type"];
|
|
@@ -4411,14 +4398,14 @@ function validateBlock(value, path34, errors) {
|
|
|
4411
4398
|
case "text": {
|
|
4412
4399
|
const text2 = value["text"];
|
|
4413
4400
|
if (typeof text2 !== "string") {
|
|
4414
|
-
error(errors, `${
|
|
4401
|
+
error(errors, `${path35}/text`, "INVALID_TEXT", "Text block text must be a string.");
|
|
4415
4402
|
return void 0;
|
|
4416
4403
|
}
|
|
4417
4404
|
if (text2.length > MAX_STRING_LENGTH) {
|
|
4418
|
-
error(errors, `${
|
|
4405
|
+
error(errors, `${path35}/text`, "TEXT_TOO_LARGE", "Text block is too large.");
|
|
4419
4406
|
return void 0;
|
|
4420
4407
|
}
|
|
4421
|
-
const cacheControl = validateCacheControl(value["cache_control"], `${
|
|
4408
|
+
const cacheControl = validateCacheControl(value["cache_control"], `${path35}/cache_control`, errors);
|
|
4422
4409
|
return cacheControl ? { type: "text", text: text2, cache_control: cacheControl } : { type: "text", text: text2 };
|
|
4423
4410
|
}
|
|
4424
4411
|
case "tool_use": {
|
|
@@ -4426,15 +4413,15 @@ function validateBlock(value, path34, errors) {
|
|
|
4426
4413
|
const name2 = value["name"];
|
|
4427
4414
|
const input = value["input"];
|
|
4428
4415
|
if (typeof id !== "string" || id.length === 0) {
|
|
4429
|
-
error(errors, `${
|
|
4416
|
+
error(errors, `${path35}/id`, "INVALID_TOOL_USE_ID", "tool_use.id must be a non-empty string.");
|
|
4430
4417
|
}
|
|
4431
4418
|
if (typeof name2 !== "string" || name2.length === 0) {
|
|
4432
|
-
error(errors, `${
|
|
4419
|
+
error(errors, `${path35}/name`, "INVALID_TOOL_NAME", "tool_use.name must be a non-empty string.");
|
|
4433
4420
|
}
|
|
4434
4421
|
if (!isPlainJsonObject(input)) {
|
|
4435
|
-
error(errors, `${
|
|
4422
|
+
error(errors, `${path35}/input`, "INVALID_TOOL_INPUT", "tool_use.input must be an object.");
|
|
4436
4423
|
}
|
|
4437
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4424
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path35}/providerMeta`, errors);
|
|
4438
4425
|
if (typeof id !== "string" || id.length === 0 || typeof name2 !== "string" || name2.length === 0 || !isPlainJsonObject(input)) {
|
|
4439
4426
|
return void 0;
|
|
4440
4427
|
}
|
|
@@ -4446,18 +4433,18 @@ function validateBlock(value, path34, errors) {
|
|
|
4446
4433
|
const content = value["content"];
|
|
4447
4434
|
const isError = value["is_error"];
|
|
4448
4435
|
if (typeof toolUseId !== "string" || toolUseId.length === 0) {
|
|
4449
|
-
error(errors, `${
|
|
4436
|
+
error(errors, `${path35}/tool_use_id`, "INVALID_TOOL_RESULT_ID", "tool_result.tool_use_id must be a non-empty string.");
|
|
4450
4437
|
}
|
|
4451
4438
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
4452
|
-
error(errors, `${
|
|
4439
|
+
error(errors, `${path35}/name`, "INVALID_TOOL_RESULT_NAME", "tool_result.name must be a string.");
|
|
4453
4440
|
}
|
|
4454
4441
|
if (typeof content !== "string") {
|
|
4455
|
-
error(errors, `${
|
|
4442
|
+
error(errors, `${path35}/content`, "INVALID_TOOL_RESULT_CONTENT", "tool_result.content must be a string.");
|
|
4456
4443
|
} else if (content.length > MAX_STRING_LENGTH) {
|
|
4457
|
-
error(errors, `${
|
|
4444
|
+
error(errors, `${path35}/content`, "TOOL_RESULT_TOO_LARGE", "tool_result.content is too large.");
|
|
4458
4445
|
}
|
|
4459
4446
|
if (isError !== void 0 && typeof isError !== "boolean") {
|
|
4460
|
-
error(errors, `${
|
|
4447
|
+
error(errors, `${path35}/is_error`, "INVALID_TOOL_RESULT_ERROR", "tool_result.is_error must be boolean.");
|
|
4461
4448
|
}
|
|
4462
4449
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || typeof content !== "string") return void 0;
|
|
4463
4450
|
return {
|
|
@@ -4471,25 +4458,25 @@ function validateBlock(value, path34, errors) {
|
|
|
4471
4458
|
case "image": {
|
|
4472
4459
|
const source = value["source"];
|
|
4473
4460
|
if (!isRecord3(source)) {
|
|
4474
|
-
error(errors, `${
|
|
4461
|
+
error(errors, `${path35}/source`, "INVALID_IMAGE_SOURCE", "image.source must be an object.");
|
|
4475
4462
|
return void 0;
|
|
4476
4463
|
}
|
|
4477
4464
|
const sourceType = source["type"];
|
|
4478
4465
|
if (sourceType !== "base64" && sourceType !== "url") {
|
|
4479
|
-
error(errors, `${
|
|
4466
|
+
error(errors, `${path35}/source/type`, "INVALID_IMAGE_SOURCE_TYPE", "image.source.type must be base64 or url.");
|
|
4480
4467
|
return void 0;
|
|
4481
4468
|
}
|
|
4482
4469
|
const mediaType = source["media_type"];
|
|
4483
4470
|
const data = source["data"];
|
|
4484
4471
|
const url = source["url"];
|
|
4485
4472
|
if (mediaType !== void 0 && typeof mediaType !== "string") {
|
|
4486
|
-
error(errors, `${
|
|
4473
|
+
error(errors, `${path35}/source/media_type`, "INVALID_IMAGE_MEDIA_TYPE", "image.source.media_type must be a string.");
|
|
4487
4474
|
}
|
|
4488
4475
|
if (data !== void 0 && typeof data !== "string") {
|
|
4489
|
-
error(errors, `${
|
|
4476
|
+
error(errors, `${path35}/source/data`, "INVALID_IMAGE_DATA", "image.source.data must be a string.");
|
|
4490
4477
|
}
|
|
4491
4478
|
if (url !== void 0 && typeof url !== "string") {
|
|
4492
|
-
error(errors, `${
|
|
4479
|
+
error(errors, `${path35}/source/url`, "INVALID_IMAGE_URL", "image.source.url must be a string.");
|
|
4493
4480
|
}
|
|
4494
4481
|
return {
|
|
4495
4482
|
type: "image",
|
|
@@ -4505,13 +4492,13 @@ function validateBlock(value, path34, errors) {
|
|
|
4505
4492
|
const thinking = value["thinking"];
|
|
4506
4493
|
const signature = value["signature"];
|
|
4507
4494
|
if (typeof thinking !== "string") {
|
|
4508
|
-
error(errors, `${
|
|
4495
|
+
error(errors, `${path35}/thinking`, "INVALID_THINKING", "thinking.thinking must be a string.");
|
|
4509
4496
|
return void 0;
|
|
4510
4497
|
}
|
|
4511
4498
|
if (signature !== void 0 && typeof signature !== "string") {
|
|
4512
|
-
error(errors, `${
|
|
4499
|
+
error(errors, `${path35}/signature`, "INVALID_THINKING_SIGNATURE", "thinking.signature must be a string.");
|
|
4513
4500
|
}
|
|
4514
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4501
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path35}/providerMeta`, errors);
|
|
4515
4502
|
return {
|
|
4516
4503
|
type: "thinking",
|
|
4517
4504
|
thinking,
|
|
@@ -4520,7 +4507,7 @@ function validateBlock(value, path34, errors) {
|
|
|
4520
4507
|
};
|
|
4521
4508
|
}
|
|
4522
4509
|
default:
|
|
4523
|
-
error(errors, `${
|
|
4510
|
+
error(errors, `${path35}/type`, "UNKNOWN_BLOCK_TYPE", `Unknown content block type: ${String(type)}`);
|
|
4524
4511
|
return void 0;
|
|
4525
4512
|
}
|
|
4526
4513
|
}
|
|
@@ -4538,39 +4525,39 @@ function validateContextEditorMessages(value, currentMessageCount = 0) {
|
|
|
4538
4525
|
error(errors, "/messages", "PAYLOAD_TOO_LARGE", "Context editor payload is too large.");
|
|
4539
4526
|
}
|
|
4540
4527
|
value.forEach((item, index) => {
|
|
4541
|
-
const
|
|
4528
|
+
const path35 = `/messages/${index}`;
|
|
4542
4529
|
if (!isRecord3(item)) {
|
|
4543
|
-
error(errors,
|
|
4530
|
+
error(errors, path35, "INVALID_MESSAGE", "Message must be an object.");
|
|
4544
4531
|
return;
|
|
4545
4532
|
}
|
|
4546
4533
|
const role = item["role"];
|
|
4547
4534
|
if (!isMessageRole(role)) {
|
|
4548
|
-
error(errors, `${
|
|
4535
|
+
error(errors, `${path35}/role`, "INVALID_ROLE", "Message role must be user, assistant, or system.");
|
|
4549
4536
|
return;
|
|
4550
4537
|
}
|
|
4551
4538
|
const rawContent = item["content"];
|
|
4552
4539
|
let content;
|
|
4553
4540
|
if (typeof rawContent === "string") {
|
|
4554
4541
|
if (rawContent.length > MAX_STRING_LENGTH) {
|
|
4555
|
-
error(errors, `${
|
|
4542
|
+
error(errors, `${path35}/content`, "CONTENT_TOO_LARGE", "Message content is too large.");
|
|
4556
4543
|
return;
|
|
4557
4544
|
}
|
|
4558
4545
|
content = rawContent;
|
|
4559
4546
|
} else if (Array.isArray(rawContent)) {
|
|
4560
4547
|
const blocks = [];
|
|
4561
4548
|
rawContent.forEach((block, blockIndex) => {
|
|
4562
|
-
const parsed = validateBlock(block, `${
|
|
4549
|
+
const parsed = validateBlock(block, `${path35}/content/${blockIndex}`, errors);
|
|
4563
4550
|
if (parsed) blocks.push(parsed);
|
|
4564
4551
|
});
|
|
4565
4552
|
content = blocks;
|
|
4566
4553
|
} else {
|
|
4567
|
-
error(errors, `${
|
|
4554
|
+
error(errors, `${path35}/content`, "INVALID_CONTENT", "Message content must be a string or content block array.");
|
|
4568
4555
|
return;
|
|
4569
4556
|
}
|
|
4570
4557
|
const ts = item["ts"];
|
|
4571
4558
|
if (ts !== void 0) {
|
|
4572
4559
|
if (typeof ts !== "string" || Number.isNaN(Date.parse(ts))) {
|
|
4573
|
-
error(errors, `${
|
|
4560
|
+
error(errors, `${path35}/ts`, "INVALID_TIMESTAMP", "Message ts must be an ISO-like timestamp string.");
|
|
4574
4561
|
return;
|
|
4575
4562
|
}
|
|
4576
4563
|
}
|
|
@@ -4959,6 +4946,7 @@ import { isSageProjectServerAvailable, SageProjectServerConnection } from "@wron
|
|
|
4959
4946
|
import {
|
|
4960
4947
|
checkCodebaseIndexServerHealth,
|
|
4961
4948
|
getIndexState,
|
|
4949
|
+
resolveProjectIndexDaemonAvailability,
|
|
4962
4950
|
shutdownCodebaseIndexServer
|
|
4963
4951
|
} from "@wrongstack/tools";
|
|
4964
4952
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
@@ -5051,6 +5039,19 @@ async function chronicleHealth(projectRoot) {
|
|
|
5051
5039
|
}
|
|
5052
5040
|
async function codebaseIndexHealth(projectRoot, indexDir) {
|
|
5053
5041
|
const startedAt = Date.now();
|
|
5042
|
+
const availability = resolveProjectIndexDaemonAvailability(projectRoot, indexDir);
|
|
5043
|
+
if (availability.kind === "endpoint-invalid") {
|
|
5044
|
+
return {
|
|
5045
|
+
id: "codebase-index",
|
|
5046
|
+
label: "Codebase index",
|
|
5047
|
+
status: "unavailable",
|
|
5048
|
+
required: false,
|
|
5049
|
+
mode: "endpoint-invalid",
|
|
5050
|
+
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.`,
|
|
5051
|
+
endpoint: availability.endpoint,
|
|
5052
|
+
latencyMs: Date.now() - startedAt
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5054
5055
|
try {
|
|
5055
5056
|
const health = await checkCodebaseIndexServerHealth(projectRoot, indexDir, {
|
|
5056
5057
|
timeoutMs: 2e3
|
|
@@ -5732,15 +5733,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5732
5733
|
if (!m) continue;
|
|
5733
5734
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5734
5735
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5735
|
-
let
|
|
5736
|
-
if (
|
|
5736
|
+
let path35 = m[3] ?? "";
|
|
5737
|
+
if (path35 === "") {
|
|
5737
5738
|
i += 1;
|
|
5738
|
-
|
|
5739
|
+
path35 = parts[i + 1] ?? parts[i] ?? "";
|
|
5739
5740
|
i += 1;
|
|
5740
5741
|
}
|
|
5741
|
-
if (!
|
|
5742
|
-
const prev = counts.get(
|
|
5743
|
-
counts.set(
|
|
5742
|
+
if (!path35) continue;
|
|
5743
|
+
const prev = counts.get(path35) ?? { added: 0, deleted: 0 };
|
|
5744
|
+
counts.set(path35, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5744
5745
|
}
|
|
5745
5746
|
};
|
|
5746
5747
|
parseNumstat(unstagedNumstat);
|
|
@@ -5752,7 +5753,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5752
5753
|
if (!rec || rec.length < 3) continue;
|
|
5753
5754
|
const x = rec[0] ?? " ";
|
|
5754
5755
|
const y = rec[1] ?? " ";
|
|
5755
|
-
const
|
|
5756
|
+
const path35 = rec.slice(3);
|
|
5756
5757
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5757
5758
|
if (isRename) i += 1;
|
|
5758
5759
|
let status;
|
|
@@ -5764,13 +5765,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5764
5765
|
else if (x === "D" || y === "D") status = "D";
|
|
5765
5766
|
else status = "M";
|
|
5766
5767
|
const staged = x !== " " && x !== "?";
|
|
5767
|
-
let added = counts.get(
|
|
5768
|
-
let deleted = counts.get(
|
|
5768
|
+
let added = counts.get(path35)?.added ?? 0;
|
|
5769
|
+
let deleted = counts.get(path35)?.deleted ?? 0;
|
|
5769
5770
|
if (status === "?") {
|
|
5770
5771
|
added = 0;
|
|
5771
5772
|
deleted = 0;
|
|
5772
5773
|
}
|
|
5773
|
-
files.push({ path:
|
|
5774
|
+
files.push({ path: path35, status, added, deleted, staged });
|
|
5774
5775
|
}
|
|
5775
5776
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5776
5777
|
} catch (err) {
|
|
@@ -5781,21 +5782,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5781
5782
|
}
|
|
5782
5783
|
}
|
|
5783
5784
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5784
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5785
|
+
async function handleGitDiff(ws, projectRoot, path35) {
|
|
5785
5786
|
const cwd = projectRoot || void 0;
|
|
5786
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5787
|
-
if (!
|
|
5787
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path35, ...extra } });
|
|
5788
|
+
if (!path35 || path35.includes("\0") || path35.includes("..") || nodePath.isAbsolute(path35)) {
|
|
5788
5789
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5789
5790
|
return;
|
|
5790
5791
|
}
|
|
5791
5792
|
try {
|
|
5792
5793
|
const git = makeGit(cwd);
|
|
5793
5794
|
const { readFile: readFile13 } = await import("node:fs/promises");
|
|
5794
|
-
const { join:
|
|
5795
|
-
const oldText = await git(["show", `HEAD:${
|
|
5795
|
+
const { join: join19 } = await import("node:path");
|
|
5796
|
+
const oldText = await git(["show", `HEAD:${path35}`]);
|
|
5796
5797
|
let newText = "";
|
|
5797
5798
|
try {
|
|
5798
|
-
const abs = cwd ?
|
|
5799
|
+
const abs = cwd ? join19(cwd, path35) : path35;
|
|
5799
5800
|
const buf = await readFile13(abs);
|
|
5800
5801
|
if (buf.includes(0)) {
|
|
5801
5802
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5857,10 +5858,7 @@ async function handleGoalSnapshotRoute(ws, msg, handlers) {
|
|
|
5857
5858
|
}
|
|
5858
5859
|
|
|
5859
5860
|
// src/server/goal-ws-handler.ts
|
|
5860
|
-
import {
|
|
5861
|
-
import {
|
|
5862
|
-
assignNickname
|
|
5863
|
-
} from "@wrongstack/core/coordination";
|
|
5861
|
+
import { assignNickname } from "@wrongstack/core/coordination";
|
|
5864
5862
|
import {
|
|
5865
5863
|
GoalAssessor,
|
|
5866
5864
|
GoalPlanner,
|
|
@@ -5868,6 +5866,7 @@ import {
|
|
|
5868
5866
|
PhaseOrchestrator,
|
|
5869
5867
|
PhaseStore
|
|
5870
5868
|
} from "@wrongstack/core/goal";
|
|
5869
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
5871
5870
|
import { WorktreeManager } from "@wrongstack/core/worktree";
|
|
5872
5871
|
|
|
5873
5872
|
// src/server/git-process.ts
|
|
@@ -5904,12 +5903,7 @@ function deriveTitle(goal) {
|
|
|
5904
5903
|
return trimmed || "Goal";
|
|
5905
5904
|
}
|
|
5906
5905
|
async function commitsSince(cwd, baseSha, branch) {
|
|
5907
|
-
const output = await gitStdout(cwd, [
|
|
5908
|
-
"log",
|
|
5909
|
-
"--reverse",
|
|
5910
|
-
"--format=%H",
|
|
5911
|
-
`${baseSha}..${branch}`
|
|
5912
|
-
]);
|
|
5906
|
+
const output = await gitStdout(cwd, ["log", "--reverse", "--format=%H", `${baseSha}..${branch}`]);
|
|
5913
5907
|
if (output === null) return [];
|
|
5914
5908
|
return output.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
5915
5909
|
}
|
|
@@ -5960,6 +5954,17 @@ var GoalWebSocketHandler = class {
|
|
|
5960
5954
|
ws.on("error", () => this.clients.delete(client));
|
|
5961
5955
|
this.sendState(client);
|
|
5962
5956
|
}
|
|
5957
|
+
/** Release timers, in-flight work, and socket references owned by this host. */
|
|
5958
|
+
dispose() {
|
|
5959
|
+
this.stopping = true;
|
|
5960
|
+
this.abort?.abort();
|
|
5961
|
+
this.abort = null;
|
|
5962
|
+
this.assessAbort?.abort();
|
|
5963
|
+
this.assessAbort = null;
|
|
5964
|
+
this.orchestrator?.stop();
|
|
5965
|
+
this.stopBroadcast();
|
|
5966
|
+
this.clients.clear();
|
|
5967
|
+
}
|
|
5963
5968
|
async handleMessage(ws, msg) {
|
|
5964
5969
|
switch (msg.type) {
|
|
5965
5970
|
case "goal.assess":
|
|
@@ -6007,7 +6012,8 @@ var GoalWebSocketHandler = class {
|
|
|
6007
6012
|
}
|
|
6008
6013
|
case "goal.assignTask": {
|
|
6009
6014
|
const { taskId, agentId, agentName } = msg.payload;
|
|
6010
|
-
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
6015
|
+
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
6016
|
+
this.afterBoardMutation();
|
|
6011
6017
|
break;
|
|
6012
6018
|
}
|
|
6013
6019
|
case "goal.addTask": {
|
|
@@ -6052,7 +6058,10 @@ var GoalWebSocketHandler = class {
|
|
|
6052
6058
|
this.graph = graph;
|
|
6053
6059
|
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
6054
6060
|
} else {
|
|
6055
|
-
this.broadcast({
|
|
6061
|
+
this.broadcast({
|
|
6062
|
+
type: "goal.error",
|
|
6063
|
+
payload: { message: `Graph not found: ${graphId}` }
|
|
6064
|
+
});
|
|
6056
6065
|
}
|
|
6057
6066
|
}
|
|
6058
6067
|
break;
|
|
@@ -6075,10 +6084,13 @@ var GoalWebSocketHandler = class {
|
|
|
6075
6084
|
const mySeq = ++this.assessSeq;
|
|
6076
6085
|
const sendResult7 = (result) => {
|
|
6077
6086
|
if (mySeq !== this.assessSeq) return;
|
|
6078
|
-
sendSerialized(
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6087
|
+
sendSerialized(
|
|
6088
|
+
ws,
|
|
6089
|
+
JSON.stringify({
|
|
6090
|
+
type: "goal.assess.result",
|
|
6091
|
+
payload: { ...result, reqSeq: seq }
|
|
6092
|
+
})
|
|
6093
|
+
);
|
|
6082
6094
|
};
|
|
6083
6095
|
if (!goal.trim()) {
|
|
6084
6096
|
sendResult7({
|
|
@@ -6124,15 +6136,24 @@ var GoalWebSocketHandler = class {
|
|
|
6124
6136
|
const multiBoard = payload?.multiBoard ?? false;
|
|
6125
6137
|
const verifyTasks = payload?.verifyTasks ?? false;
|
|
6126
6138
|
const chimeraReview = payload?.chimeraReview ?? false;
|
|
6127
|
-
|
|
6139
|
+
const runAbort = new AbortController();
|
|
6140
|
+
this.abort = runAbort;
|
|
6128
6141
|
this.stopping = false;
|
|
6129
|
-
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal,
|
|
6130
|
-
if (this.stopping ||
|
|
6142
|
+
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal, runAbort.signal);
|
|
6143
|
+
if (this.stopping || runAbort.signal.aborted) {
|
|
6131
6144
|
this.broadcast({ type: "goal.stopped", payload: { title } });
|
|
6132
6145
|
return;
|
|
6133
6146
|
}
|
|
6134
6147
|
this.logger.info(`[Goal] Starting: ${title}`);
|
|
6135
|
-
const graph = await new PhaseGraphBuilder({
|
|
6148
|
+
const graph = await new PhaseGraphBuilder({
|
|
6149
|
+
title,
|
|
6150
|
+
description: goal,
|
|
6151
|
+
phases,
|
|
6152
|
+
autonomous,
|
|
6153
|
+
multiBoard,
|
|
6154
|
+
verifyTasks,
|
|
6155
|
+
chimeraReview
|
|
6156
|
+
}).build();
|
|
6136
6157
|
this.graph = graph;
|
|
6137
6158
|
await this.store.save(graph);
|
|
6138
6159
|
const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
|
|
@@ -6151,9 +6172,10 @@ var GoalWebSocketHandler = class {
|
|
|
6151
6172
|
maybeVerify.verifyPhase = (async (phase, env) => {
|
|
6152
6173
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
6153
6174
|
try {
|
|
6154
|
-
const {
|
|
6175
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
6155
6176
|
const result = await new Promise((resolve16) => {
|
|
6156
|
-
|
|
6177
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
6178
|
+
execFile2(npxCommand, ["tsc", "--noEmit"], { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
6157
6179
|
if (err && err.code === "ENOENT") {
|
|
6158
6180
|
resolve16("[verify] tsc not found \u2014 skipping");
|
|
6159
6181
|
return;
|
|
@@ -6295,11 +6317,41 @@ Run npx tsc --noEmit to verify the fix. Output the fixed file paths.`;
|
|
|
6295
6317
|
/** Generic fallback phases when the LLM planner produces nothing usable. */
|
|
6296
6318
|
defaultPhases() {
|
|
6297
6319
|
return [
|
|
6298
|
-
{
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6320
|
+
{
|
|
6321
|
+
name: "Discovery",
|
|
6322
|
+
description: "Requirements gathering",
|
|
6323
|
+
priority: "high",
|
|
6324
|
+
estimateHours: 2,
|
|
6325
|
+
parallelizable: false
|
|
6326
|
+
},
|
|
6327
|
+
{
|
|
6328
|
+
name: "Design",
|
|
6329
|
+
description: "Architecture and design",
|
|
6330
|
+
priority: "critical",
|
|
6331
|
+
estimateHours: 4,
|
|
6332
|
+
parallelizable: false
|
|
6333
|
+
},
|
|
6334
|
+
{
|
|
6335
|
+
name: "Implementation",
|
|
6336
|
+
description: "Core development",
|
|
6337
|
+
priority: "critical",
|
|
6338
|
+
estimateHours: 12,
|
|
6339
|
+
parallelizable: false
|
|
6340
|
+
},
|
|
6341
|
+
{
|
|
6342
|
+
name: "Testing",
|
|
6343
|
+
description: "Unit and integration tests",
|
|
6344
|
+
priority: "high",
|
|
6345
|
+
estimateHours: 6,
|
|
6346
|
+
parallelizable: true
|
|
6347
|
+
},
|
|
6348
|
+
{
|
|
6349
|
+
name: "Deployment",
|
|
6350
|
+
description: "Deploy to production",
|
|
6351
|
+
priority: "medium",
|
|
6352
|
+
estimateHours: 2,
|
|
6353
|
+
parallelizable: false
|
|
6354
|
+
}
|
|
6303
6355
|
];
|
|
6304
6356
|
}
|
|
6305
6357
|
/** Plan phases+todos for the goal via the LLM; fall back to defaults on failure.
|
|
@@ -6380,8 +6432,10 @@ Type: ${task.type}`;
|
|
|
6380
6432
|
try {
|
|
6381
6433
|
const result_ = await this.agent.run(reviewPrompt);
|
|
6382
6434
|
if (result_.status === "done" && result_.finalText) {
|
|
6383
|
-
this.logger.info(
|
|
6384
|
-
${
|
|
6435
|
+
this.logger.info(
|
|
6436
|
+
`[Goal] Chimera review for "${task.title}":
|
|
6437
|
+
${result_.finalText.slice(0, 2e3)}`
|
|
6438
|
+
);
|
|
6385
6439
|
}
|
|
6386
6440
|
} catch (err) {
|
|
6387
6441
|
this.logger.warn(`[Goal] Chimera review failed for "${task.title}": ${toErrorMessage2(err)}`);
|
|
@@ -6434,7 +6488,16 @@ ${result_.finalText.slice(0, 2e3)}`);
|
|
|
6434
6488
|
}
|
|
6435
6489
|
buildState(activePhaseId) {
|
|
6436
6490
|
if (!this.graph) {
|
|
6437
|
-
return {
|
|
6491
|
+
return {
|
|
6492
|
+
phases: [],
|
|
6493
|
+
tasks: [],
|
|
6494
|
+
overallPercent: 0,
|
|
6495
|
+
autonomous: true,
|
|
6496
|
+
title: "",
|
|
6497
|
+
multiBoard: false,
|
|
6498
|
+
verifyTasks: false,
|
|
6499
|
+
chimeraReview: false
|
|
6500
|
+
};
|
|
6438
6501
|
}
|
|
6439
6502
|
const phases = Array.from(this.graph.phases.values());
|
|
6440
6503
|
const currentActiveId = activePhaseId || phases.find((p) => p.status === "running")?.id || phases[0]?.id || "";
|
|
@@ -6940,9 +7003,9 @@ function getAnalyticsBuffer() {
|
|
|
6940
7003
|
}
|
|
6941
7004
|
|
|
6942
7005
|
// src/server/http-server.ts
|
|
6943
|
-
import * as
|
|
7006
|
+
import * as fs10 from "node:fs/promises";
|
|
6944
7007
|
import * as http from "node:http";
|
|
6945
|
-
import * as
|
|
7008
|
+
import * as path13 from "node:path";
|
|
6946
7009
|
import * as v8 from "node:v8";
|
|
6947
7010
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6948
7011
|
|
|
@@ -7626,18 +7689,153 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7626
7689
|
}
|
|
7627
7690
|
}
|
|
7628
7691
|
|
|
7629
|
-
// src/server/
|
|
7692
|
+
// src/server/memory-diagnostics.ts
|
|
7630
7693
|
import * as fs8 from "node:fs/promises";
|
|
7631
7694
|
import * as path11 from "node:path";
|
|
7695
|
+
var DEFAULT_TAIL_BYTES = 1024 * 1024;
|
|
7696
|
+
var MAX_PROCESSES = 32;
|
|
7697
|
+
function finiteNumber(record2, key) {
|
|
7698
|
+
const value = record2[key];
|
|
7699
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7700
|
+
}
|
|
7701
|
+
function stringValue(record2, key) {
|
|
7702
|
+
const value = record2[key];
|
|
7703
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7704
|
+
}
|
|
7705
|
+
function toProcessDiagnostic(record2) {
|
|
7706
|
+
const pid = finiteNumber(record2, "pid");
|
|
7707
|
+
const ts = stringValue(record2, "ts");
|
|
7708
|
+
if (pid === void 0 || ts === void 0) return null;
|
|
7709
|
+
const queueEntries = finiteNumber(record2, "hqQueueEntries");
|
|
7710
|
+
const queueBytes = finiteNumber(record2, "hqQueueBytes");
|
|
7711
|
+
const queueMaxBytes = finiteNumber(record2, "hqQueueMaxBytes");
|
|
7712
|
+
const snapshotInFlight = finiteNumber(record2, "hqSnapshotInFlight");
|
|
7713
|
+
const snapshotPending = finiteNumber(record2, "hqSnapshotPending");
|
|
7714
|
+
const snapshotTimerScheduled = finiteNumber(record2, "hqSnapshotTimerScheduled");
|
|
7715
|
+
const eventInFlight = finiteNumber(record2, "hqEventInFlight");
|
|
7716
|
+
const eventPending = finiteNumber(record2, "hqEventPending");
|
|
7717
|
+
const eventCoalesced = finiteNumber(record2, "hqEventCoalesced");
|
|
7718
|
+
const eventDropped = finiteNumber(record2, "hqEventDropped");
|
|
7719
|
+
return {
|
|
7720
|
+
pid,
|
|
7721
|
+
surface: stringValue(record2, "surface") ?? "unknown",
|
|
7722
|
+
...stringValue(record2, "sessionId") !== void 0 ? { sessionId: stringValue(record2, "sessionId") } : {},
|
|
7723
|
+
ts,
|
|
7724
|
+
memory: {
|
|
7725
|
+
rss: finiteNumber(record2, "rss") ?? 0,
|
|
7726
|
+
heapUsed: finiteNumber(record2, "heapUsed") ?? 0,
|
|
7727
|
+
heapTotal: finiteNumber(record2, "heapTotal") ?? 0,
|
|
7728
|
+
retainedHeapUsed: finiteNumber(record2, "retainedHeapUsed"),
|
|
7729
|
+
nativeResidual: finiteNumber(record2, "nativeResidual"),
|
|
7730
|
+
external: finiteNumber(record2, "external"),
|
|
7731
|
+
arrayBuffers: finiteNumber(record2, "arrayBuffers")
|
|
7732
|
+
},
|
|
7733
|
+
signal: stringValue(record2, "memorySignal"),
|
|
7734
|
+
heapGrowthBytesPerHour: finiteNumber(record2, "heapGrowthBytesPerHour"),
|
|
7735
|
+
rssGrowthBytesPerHour: finiteNumber(record2, "rssGrowthBytesPerHour"),
|
|
7736
|
+
workload: {
|
|
7737
|
+
messages: finiteNumber(record2, "messages"),
|
|
7738
|
+
messageEstimatedTokens: finiteNumber(record2, "messageEstimatedTokens"),
|
|
7739
|
+
historyEntries: finiteNumber(record2, "historyEntries"),
|
|
7740
|
+
historyMountedEntries: finiteNumber(record2, "historyMountedEntries"),
|
|
7741
|
+
historyCachedGroups: finiteNumber(record2, "historyCachedGroups"),
|
|
7742
|
+
appRenders: finiteNumber(record2, "appRenders"),
|
|
7743
|
+
metricsDroppedObservations: finiteNumber(record2, "metricsDroppedObservations"),
|
|
7744
|
+
kanbanSyncActive: finiteNumber(record2, "kanbanSyncActive") === 1,
|
|
7745
|
+
kanbanSyncPendingBoards: finiteNumber(record2, "kanbanSyncPendingBoards"),
|
|
7746
|
+
kanbanSyncFullRescanPending: finiteNumber(record2, "kanbanSyncFullRescanPending") === 1,
|
|
7747
|
+
kanbanSyncRemoteApplyQueued: finiteNumber(record2, "kanbanSyncRemoteApplyQueued") === 1,
|
|
7748
|
+
kanbanSyncPendingRemoteBoards: finiteNumber(record2, "kanbanSyncPendingRemoteBoards"),
|
|
7749
|
+
kanbanSyncPublishRuns: finiteNumber(record2, "kanbanSyncPublishRuns"),
|
|
7750
|
+
kanbanSyncCoalescedRefreshes: finiteNumber(record2, "kanbanSyncCoalescedRefreshes"),
|
|
7751
|
+
kanbanSupervisorSnapshots: finiteNumber(record2, "kanbanSupervisorSnapshots"),
|
|
7752
|
+
kanbanSupervisorScheduledBoards: finiteNumber(record2, "kanbanSupervisorScheduledBoards"),
|
|
7753
|
+
kanbanSupervisorAgentCooldowns: finiteNumber(record2, "kanbanSupervisorAgentCooldowns"),
|
|
7754
|
+
kanbanSupervisorRunningAgents: finiteNumber(record2, "kanbanSupervisorRunningAgents")
|
|
7755
|
+
},
|
|
7756
|
+
resources: {
|
|
7757
|
+
active: finiteNumber(record2, "activeResources"),
|
|
7758
|
+
types: stringValue(record2, "activeResourceTypes")
|
|
7759
|
+
},
|
|
7760
|
+
...queueEntries !== void 0 && queueBytes !== void 0 && queueMaxBytes !== void 0 ? {
|
|
7761
|
+
hqQueue: {
|
|
7762
|
+
entries: queueEntries,
|
|
7763
|
+
bytes: queueBytes,
|
|
7764
|
+
maxBytes: queueMaxBytes,
|
|
7765
|
+
droppedFrames: finiteNumber(record2, "hqQueueDroppedFrames") ?? 0,
|
|
7766
|
+
droppedBytes: finiteNumber(record2, "hqQueueDroppedBytes") ?? 0,
|
|
7767
|
+
coalescedFrames: finiteNumber(record2, "hqQueueCoalescedFrames") ?? 0,
|
|
7768
|
+
coalescedBytes: finiteNumber(record2, "hqQueueCoalescedBytes") ?? 0
|
|
7769
|
+
}
|
|
7770
|
+
} : {},
|
|
7771
|
+
...snapshotInFlight !== void 0 || snapshotPending !== void 0 || snapshotTimerScheduled !== void 0 ? {
|
|
7772
|
+
hqSnapshot: {
|
|
7773
|
+
inFlight: snapshotInFlight === 1,
|
|
7774
|
+
pending: snapshotPending === 1,
|
|
7775
|
+
timerScheduled: snapshotTimerScheduled === 1,
|
|
7776
|
+
eventInFlight: eventInFlight === 1,
|
|
7777
|
+
pendingEvents: eventPending ?? 0,
|
|
7778
|
+
coalescedEvents: eventCoalesced ?? 0,
|
|
7779
|
+
droppedEvents: eventDropped ?? 0
|
|
7780
|
+
}
|
|
7781
|
+
} : {},
|
|
7782
|
+
profileTopStack: stringValue(record2, "memoryProfileTopStack"),
|
|
7783
|
+
profileTopStackBytes: finiteNumber(record2, "memoryProfileTopStackBytes")
|
|
7784
|
+
};
|
|
7785
|
+
}
|
|
7786
|
+
async function readRecentProcessMemoryDiagnostics(globalRoot, tailBytes = DEFAULT_TAIL_BYTES) {
|
|
7787
|
+
if (!globalRoot) return [];
|
|
7788
|
+
const heapLog = path11.join(globalRoot, "logs", "heap.jsonl");
|
|
7789
|
+
let handle;
|
|
7790
|
+
try {
|
|
7791
|
+
handle = await fs8.open(heapLog, "r");
|
|
7792
|
+
const stat3 = await handle.stat();
|
|
7793
|
+
const length = Math.min(stat3.size, Math.max(1, tailBytes));
|
|
7794
|
+
if (length === 0) return [];
|
|
7795
|
+
const start = stat3.size - length;
|
|
7796
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
7797
|
+
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
7798
|
+
let text2 = buffer.subarray(0, bytesRead).toString("utf8");
|
|
7799
|
+
if (start > 0) {
|
|
7800
|
+
const firstNewline = text2.indexOf("\n");
|
|
7801
|
+
if (firstNewline === -1) return [];
|
|
7802
|
+
text2 = text2.slice(firstNewline + 1);
|
|
7803
|
+
}
|
|
7804
|
+
const newestByPid = /* @__PURE__ */ new Map();
|
|
7805
|
+
const lines = text2.split(/\r?\n/u);
|
|
7806
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
7807
|
+
const line = lines[index]?.trim();
|
|
7808
|
+
if (!line) continue;
|
|
7809
|
+
try {
|
|
7810
|
+
const parsed = JSON.parse(line);
|
|
7811
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
|
|
7812
|
+
const diagnostic = toProcessDiagnostic(parsed);
|
|
7813
|
+
if (!diagnostic || newestByPid.has(diagnostic.pid)) continue;
|
|
7814
|
+
newestByPid.set(diagnostic.pid, diagnostic);
|
|
7815
|
+
if (newestByPid.size >= MAX_PROCESSES) break;
|
|
7816
|
+
} catch {
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
return [...newestByPid.values()].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
7820
|
+
} catch {
|
|
7821
|
+
return [];
|
|
7822
|
+
} finally {
|
|
7823
|
+
await handle?.close().catch(() => void 0);
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7826
|
+
|
|
7827
|
+
// src/server/projects-manifest.ts
|
|
7828
|
+
import * as fs9 from "node:fs/promises";
|
|
7829
|
+
import * as path12 from "node:path";
|
|
7632
7830
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7633
7831
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7634
7832
|
function projectsJsonPath(globalConfigPath) {
|
|
7635
|
-
const base =
|
|
7636
|
-
return
|
|
7833
|
+
const base = path12.dirname(globalConfigPath);
|
|
7834
|
+
return path12.join(base, "projects.json");
|
|
7637
7835
|
}
|
|
7638
7836
|
async function loadManifest(globalConfigPath) {
|
|
7639
7837
|
try {
|
|
7640
|
-
const raw = await
|
|
7838
|
+
const raw = await fs9.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
7641
7839
|
const parsed = JSON.parse(raw);
|
|
7642
7840
|
return { projects: parsed.projects ?? [] };
|
|
7643
7841
|
} catch {
|
|
@@ -7646,37 +7844,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7646
7844
|
}
|
|
7647
7845
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7648
7846
|
const file = projectsJsonPath(globalConfigPath);
|
|
7649
|
-
await
|
|
7650
|
-
await
|
|
7847
|
+
await fs9.mkdir(path12.dirname(file), { recursive: true });
|
|
7848
|
+
await fs9.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7651
7849
|
}
|
|
7652
7850
|
function generateProjectSlug(rootPath) {
|
|
7653
7851
|
return projectSlug(rootPath);
|
|
7654
7852
|
}
|
|
7655
7853
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7656
|
-
const base =
|
|
7657
|
-
const dir =
|
|
7658
|
-
await
|
|
7854
|
+
const base = path12.dirname(globalConfigPath);
|
|
7855
|
+
const dir = path12.join(base, "projects", slug);
|
|
7856
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
7659
7857
|
return dir;
|
|
7660
7858
|
}
|
|
7661
7859
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7662
|
-
const root =
|
|
7860
|
+
const root = path12.resolve(options.projectRoot);
|
|
7663
7861
|
const file = projectsJsonPath(globalConfigPath);
|
|
7664
7862
|
let entry;
|
|
7665
7863
|
await withFileLock(file, async () => {
|
|
7666
7864
|
const manifest = await loadManifest(globalConfigPath);
|
|
7667
7865
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7668
|
-
entry = manifest.projects.find((candidate) =>
|
|
7866
|
+
entry = manifest.projects.find((candidate) => path12.resolve(candidate.root) === root);
|
|
7669
7867
|
if (entry) {
|
|
7670
7868
|
entry.lastSeen = now;
|
|
7671
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7869
|
+
if (options.workingDir) entry.lastWorkingDir = path12.resolve(options.workingDir);
|
|
7672
7870
|
} else {
|
|
7673
7871
|
entry = {
|
|
7674
|
-
name: options.name ??
|
|
7872
|
+
name: options.name ?? path12.basename(root),
|
|
7675
7873
|
root,
|
|
7676
7874
|
slug: generateProjectSlug(root),
|
|
7677
7875
|
createdAt: now,
|
|
7678
7876
|
lastSeen: now,
|
|
7679
|
-
lastWorkingDir: options.workingDir ?
|
|
7877
|
+
lastWorkingDir: options.workingDir ? path12.resolve(options.workingDir) : void 0
|
|
7680
7878
|
};
|
|
7681
7879
|
manifest.projects.push(entry);
|
|
7682
7880
|
}
|
|
@@ -8087,9 +8285,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
8087
8285
|
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'`;
|
|
8088
8286
|
}
|
|
8089
8287
|
function isInsideDist(candidate, distDir) {
|
|
8090
|
-
const root =
|
|
8091
|
-
const resolved =
|
|
8092
|
-
return resolved === root || resolved.startsWith(root +
|
|
8288
|
+
const root = path13.resolve(distDir);
|
|
8289
|
+
const resolved = path13.resolve(candidate);
|
|
8290
|
+
return resolved === root || resolved.startsWith(root + path13.sep);
|
|
8093
8291
|
}
|
|
8094
8292
|
function decodeSessionId(segment) {
|
|
8095
8293
|
try {
|
|
@@ -8109,7 +8307,7 @@ function strictDecodeParam(segment, res) {
|
|
|
8109
8307
|
}
|
|
8110
8308
|
function createHttpServer(opts) {
|
|
8111
8309
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
8112
|
-
const distDir =
|
|
8310
|
+
const distDir = path13.resolve(opts.distDir);
|
|
8113
8311
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
8114
8312
|
let techStackRuntime = null;
|
|
8115
8313
|
const getTechStackRuntime = async () => {
|
|
@@ -8437,11 +8635,7 @@ function createHttpServer(opts) {
|
|
|
8437
8635
|
if (researchMatch && req.method === "POST") {
|
|
8438
8636
|
const pkg = strictDecodeParam(researchMatch[1], res);
|
|
8439
8637
|
if (pkg === null) return;
|
|
8440
|
-
await handleTechStackDependencyResearch(
|
|
8441
|
-
res,
|
|
8442
|
-
deps2,
|
|
8443
|
-
pkg
|
|
8444
|
-
);
|
|
8638
|
+
await handleTechStackDependencyResearch(res, deps2, pkg);
|
|
8445
8639
|
return;
|
|
8446
8640
|
}
|
|
8447
8641
|
} catch (error2) {
|
|
@@ -8477,6 +8671,7 @@ function createHttpServer(opts) {
|
|
|
8477
8671
|
return;
|
|
8478
8672
|
}
|
|
8479
8673
|
if (url.pathname === "/debug/system" && req.method === "GET") {
|
|
8674
|
+
const processes = await readRecentProcessMemoryDiagnostics(opts.globalRoot);
|
|
8480
8675
|
res.writeHead(200, {
|
|
8481
8676
|
"Content-Type": "application/json",
|
|
8482
8677
|
"Cache-Control": "no-store"
|
|
@@ -8489,6 +8684,7 @@ function createHttpServer(opts) {
|
|
|
8489
8684
|
uptime: process.uptime(),
|
|
8490
8685
|
cpuUsage: process.cpuUsage(),
|
|
8491
8686
|
codebaseIndexServer: getIndexState2().server,
|
|
8687
|
+
processes,
|
|
8492
8688
|
timestamp: Date.now()
|
|
8493
8689
|
})
|
|
8494
8690
|
);
|
|
@@ -8496,24 +8692,24 @@ function createHttpServer(opts) {
|
|
|
8496
8692
|
}
|
|
8497
8693
|
let filePath;
|
|
8498
8694
|
if (url.pathname === "/" || url.pathname === "") {
|
|
8499
|
-
filePath =
|
|
8695
|
+
filePath = path13.join(distDir, "index.html");
|
|
8500
8696
|
} else {
|
|
8501
|
-
filePath =
|
|
8697
|
+
filePath = path13.join(distDir, url.pathname);
|
|
8502
8698
|
}
|
|
8503
|
-
const resolvedPath =
|
|
8699
|
+
const resolvedPath = path13.resolve(filePath);
|
|
8504
8700
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
8505
8701
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
8506
8702
|
res.end("Forbidden");
|
|
8507
8703
|
return;
|
|
8508
8704
|
}
|
|
8509
|
-
const ext =
|
|
8705
|
+
const ext = path13.extname(resolvedPath);
|
|
8510
8706
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
8511
8707
|
res.setHeader("Content-Type", contentType);
|
|
8512
8708
|
setStaticSecurityHeaders(res);
|
|
8513
8709
|
if (ext === ".html") {
|
|
8514
8710
|
if (!shouldSetAuthCookie) res.setHeader("Cache-Control", "no-cache");
|
|
8515
8711
|
res.setHeader("Content-Security-Policy", buildCspHeader(opts.publicWsUrl, opts.host, port));
|
|
8516
|
-
const html = await
|
|
8712
|
+
const html = await fs10.readFile(resolvedPath, "utf8");
|
|
8517
8713
|
res.writeHead(200);
|
|
8518
8714
|
res.end(injectWsConfig(html, { publicWsUrl: opts.publicWsUrl }));
|
|
8519
8715
|
return;
|
|
@@ -8524,13 +8720,13 @@ function createHttpServer(opts) {
|
|
|
8524
8720
|
url.pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600"
|
|
8525
8721
|
);
|
|
8526
8722
|
}
|
|
8527
|
-
const fileContent = await
|
|
8723
|
+
const fileContent = await fs10.readFile(resolvedPath);
|
|
8528
8724
|
res.writeHead(200);
|
|
8529
8725
|
res.end(fileContent);
|
|
8530
8726
|
} catch (err) {
|
|
8531
8727
|
if (err.code === "ENOENT") {
|
|
8532
8728
|
try {
|
|
8533
|
-
const html = await
|
|
8729
|
+
const html = await fs10.readFile(path13.join(distDir, "index.html"), "utf8");
|
|
8534
8730
|
setStaticSecurityHeaders(res);
|
|
8535
8731
|
res.writeHead(200, {
|
|
8536
8732
|
"Content-Type": "text/html",
|
|
@@ -8560,14 +8756,14 @@ function createHttpServer(opts) {
|
|
|
8560
8756
|
|
|
8561
8757
|
// src/server/instance-registry.ts
|
|
8562
8758
|
import * as os from "node:os";
|
|
8563
|
-
import * as
|
|
8564
|
-
import * as
|
|
8759
|
+
import * as path14 from "node:path";
|
|
8760
|
+
import * as fs11 from "node:fs/promises";
|
|
8565
8761
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8566
8762
|
function defaultBaseDir() {
|
|
8567
|
-
return
|
|
8763
|
+
return path14.join(os.homedir(), ".wrongstack");
|
|
8568
8764
|
}
|
|
8569
8765
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8570
|
-
return
|
|
8766
|
+
return path14.join(baseDir, "webui-instances.json");
|
|
8571
8767
|
}
|
|
8572
8768
|
function isPidAlive(pid) {
|
|
8573
8769
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -8580,7 +8776,7 @@ function isPidAlive(pid) {
|
|
|
8580
8776
|
}
|
|
8581
8777
|
async function load(file) {
|
|
8582
8778
|
try {
|
|
8583
|
-
const raw = await
|
|
8779
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
8584
8780
|
const parsed = JSON.parse(raw);
|
|
8585
8781
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
8586
8782
|
return parsed;
|
|
@@ -10437,14 +10633,14 @@ function registerShutdownHandlers(res) {
|
|
|
10437
10633
|
}
|
|
10438
10634
|
|
|
10439
10635
|
// src/server/config-doctor.ts
|
|
10440
|
-
import * as
|
|
10636
|
+
import * as fs12 from "node:fs/promises";
|
|
10441
10637
|
import {
|
|
10442
10638
|
repairConfigDefaults
|
|
10443
10639
|
} from "@wrongstack/core/storage";
|
|
10444
10640
|
import { atomicWrite as atomicWrite5 } from "@wrongstack/core/utils";
|
|
10445
10641
|
import { decryptConfigSecrets } from "@wrongstack/core/security";
|
|
10446
10642
|
async function readConfig(file, vault) {
|
|
10447
|
-
const raw = await
|
|
10643
|
+
const raw = await fs12.readFile(file, "utf8");
|
|
10448
10644
|
const parsed = JSON.parse(raw);
|
|
10449
10645
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10450
10646
|
throw new Error("Active profile config must contain a JSON object.");
|
|
@@ -10508,6 +10704,7 @@ async function handleConfigDoctor(ws, apply, deps2) {
|
|
|
10508
10704
|
|
|
10509
10705
|
// src/server/mailbox-handlers.ts
|
|
10510
10706
|
import {
|
|
10707
|
+
actionToAckInput,
|
|
10511
10708
|
getSharedProjectMailbox,
|
|
10512
10709
|
isMailboxMessageVisibleTo,
|
|
10513
10710
|
MAILBOX_TYPE_PROPERTIES,
|
|
@@ -10524,6 +10721,43 @@ function getMailboxForDeps(deps2) {
|
|
|
10524
10721
|
const dir = resolveProjectDir(projectRoot, globalRoot);
|
|
10525
10722
|
return getSharedProjectMailbox(dir, deps2.events);
|
|
10526
10723
|
}
|
|
10724
|
+
async function handleMailboxAction(ws, deps2, payload) {
|
|
10725
|
+
const mb = getMailboxForDeps(deps2);
|
|
10726
|
+
if (!mb) {
|
|
10727
|
+
send(ws, {
|
|
10728
|
+
type: "mailbox.action_result",
|
|
10729
|
+
payload: {
|
|
10730
|
+
requestId: payload.requestId,
|
|
10731
|
+
success: false,
|
|
10732
|
+
error: "No project root available"
|
|
10733
|
+
}
|
|
10734
|
+
});
|
|
10735
|
+
return;
|
|
10736
|
+
}
|
|
10737
|
+
try {
|
|
10738
|
+
const message = payload.action === "soft-delete" ? await mb.softDelete(payload.mailId, payload.readerId) : await mb.ack(actionToAckInput(payload.action, payload));
|
|
10739
|
+
send(ws, {
|
|
10740
|
+
type: "mailbox.action_result",
|
|
10741
|
+
payload: {
|
|
10742
|
+
requestId: payload.requestId,
|
|
10743
|
+
success: message !== null,
|
|
10744
|
+
action: payload.action,
|
|
10745
|
+
mailId: payload.mailId
|
|
10746
|
+
}
|
|
10747
|
+
});
|
|
10748
|
+
} catch (err) {
|
|
10749
|
+
send(ws, {
|
|
10750
|
+
type: "mailbox.action_result",
|
|
10751
|
+
payload: {
|
|
10752
|
+
requestId: payload.requestId,
|
|
10753
|
+
success: false,
|
|
10754
|
+
action: payload.action,
|
|
10755
|
+
mailId: payload.mailId,
|
|
10756
|
+
error: errMessage(err)
|
|
10757
|
+
}
|
|
10758
|
+
});
|
|
10759
|
+
}
|
|
10760
|
+
}
|
|
10527
10761
|
async function handleMailboxSend(ws, deps2, payload) {
|
|
10528
10762
|
const mb = getMailboxForDeps(deps2);
|
|
10529
10763
|
if (!mb) {
|
|
@@ -10539,7 +10773,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10539
10773
|
}
|
|
10540
10774
|
try {
|
|
10541
10775
|
const message = await mb.send({
|
|
10542
|
-
from: "webui",
|
|
10776
|
+
from: payload.from ?? "webui",
|
|
10543
10777
|
to: payload.to,
|
|
10544
10778
|
type: payload.type,
|
|
10545
10779
|
audience: payload.audience,
|
|
@@ -10554,6 +10788,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10554
10788
|
requestId: payload.requestId,
|
|
10555
10789
|
success: true,
|
|
10556
10790
|
messageId: message.id,
|
|
10791
|
+
from: message.from,
|
|
10557
10792
|
to: message.to,
|
|
10558
10793
|
audience: message.audience ?? "all"
|
|
10559
10794
|
}
|
|
@@ -10598,6 +10833,7 @@ async function handleMailboxMessages(ws, deps2, payload) {
|
|
|
10598
10833
|
send(ws, {
|
|
10599
10834
|
type: "mailbox.messages",
|
|
10600
10835
|
payload: {
|
|
10836
|
+
...payload?.unreadOnly === true ? { unreadOnly: true } : {},
|
|
10601
10837
|
messages: visibleMessages.map((m) => {
|
|
10602
10838
|
const readByMe = payload?.agentId !== void 0 ? payload.agentId in m.readBy : false;
|
|
10603
10839
|
const completedByMe = payload?.agentId !== void 0 ? m.completedBy === payload.agentId : false;
|
|
@@ -10713,6 +10949,14 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10713
10949
|
...ctx.events ? { events: ctx.events } : {}
|
|
10714
10950
|
};
|
|
10715
10951
|
return {
|
|
10952
|
+
action: (ws, msg) => {
|
|
10953
|
+
const parsed = validateMailboxActionPayload(msg.payload);
|
|
10954
|
+
if (!parsed.ok) {
|
|
10955
|
+
sendResult2(ws, false, parsed.message);
|
|
10956
|
+
return;
|
|
10957
|
+
}
|
|
10958
|
+
return handleMailboxAction(ws, deps2, parsed.value);
|
|
10959
|
+
},
|
|
10716
10960
|
send: (ws, msg) => {
|
|
10717
10961
|
const parsed = validateMailboxSendPayload(msg.payload);
|
|
10718
10962
|
if (!parsed.ok) {
|
|
@@ -10763,6 +11007,9 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10763
11007
|
}
|
|
10764
11008
|
async function handleMailboxRoute(ws, msg, handlers) {
|
|
10765
11009
|
switch (msg.type) {
|
|
11010
|
+
case "mailbox.action":
|
|
11011
|
+
await handlers.action(ws, msg);
|
|
11012
|
+
return true;
|
|
10766
11013
|
case "mailbox.send":
|
|
10767
11014
|
await handlers.send(ws, msg);
|
|
10768
11015
|
return true;
|
|
@@ -12076,10 +12323,10 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
12076
12323
|
import { spawn as spawn2 } from "node:child_process";
|
|
12077
12324
|
import { existsSync } from "node:fs";
|
|
12078
12325
|
import { findPackageJSON } from "node:module";
|
|
12079
|
-
import * as
|
|
12326
|
+
import * as path15 from "node:path";
|
|
12080
12327
|
function resolveDistDir(input) {
|
|
12081
12328
|
const options = typeof input === "string" ? { explicitDistDir: input } : input ?? {};
|
|
12082
|
-
if (options.explicitDistDir) return
|
|
12329
|
+
if (options.explicitDistDir) return path15.resolve(options.explicitDistDir);
|
|
12083
12330
|
const exists = options.exists ?? existsSync;
|
|
12084
12331
|
let packageTarget;
|
|
12085
12332
|
try {
|
|
@@ -12091,15 +12338,15 @@ function resolveDistDir(input) {
|
|
|
12091
12338
|
);
|
|
12092
12339
|
}
|
|
12093
12340
|
if (!packageTarget) return null;
|
|
12094
|
-
const distDir =
|
|
12341
|
+
const distDir = path15.basename(packageTarget) === "package.json" ? path15.join(path15.dirname(packageTarget), "dist") : path15.dirname(packageTarget);
|
|
12095
12342
|
if (options.exists === void 0 && options.resolvePackageJson) return distDir;
|
|
12096
|
-
return exists(
|
|
12343
|
+
return exists(path15.join(distDir, "index.html")) ? distDir : null;
|
|
12097
12344
|
}
|
|
12098
12345
|
async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
12099
12346
|
const exists = deps2.exists ?? existsSync;
|
|
12100
12347
|
if (explicitDistDir) {
|
|
12101
|
-
const resolved2 =
|
|
12102
|
-
return exists(
|
|
12348
|
+
const resolved2 = path15.resolve(explicitDistDir);
|
|
12349
|
+
return exists(path15.join(resolved2, "index.html")) ? resolved2 : null;
|
|
12103
12350
|
}
|
|
12104
12351
|
const resolveOptions = {
|
|
12105
12352
|
resolvePackageJson: deps2.resolvePackageJson,
|
|
@@ -12111,7 +12358,7 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
12111
12358
|
try {
|
|
12112
12359
|
const packageJson = deps2.resolvePackageJson ? deps2.resolvePackageJson("@wrongstack/webui/package.json") : findPackageJSON("@wrongstack/webui", import.meta.url);
|
|
12113
12360
|
if (!packageJson) throw new Error("not found");
|
|
12114
|
-
packageDir =
|
|
12361
|
+
packageDir = path15.dirname(packageJson);
|
|
12115
12362
|
} catch {
|
|
12116
12363
|
throw new Error(
|
|
12117
12364
|
"@wrongstack/webui package could not be resolved. Install workspace dependencies and rebuild the CLI."
|
|
@@ -12120,8 +12367,8 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
12120
12367
|
const findRoot = deps2.findWorkspaceRoot ?? ((pkgDir) => {
|
|
12121
12368
|
let dir = pkgDir;
|
|
12122
12369
|
for (let i = 0; i < 10; i++) {
|
|
12123
|
-
if (existsSync(
|
|
12124
|
-
const parent =
|
|
12370
|
+
if (existsSync(path15.join(dir, "pnpm-workspace.yaml"))) return dir;
|
|
12371
|
+
const parent = path15.dirname(dir);
|
|
12125
12372
|
if (parent === dir) return null;
|
|
12126
12373
|
dir = parent;
|
|
12127
12374
|
}
|
|
@@ -12205,7 +12452,7 @@ function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
|
12205
12452
|
}
|
|
12206
12453
|
|
|
12207
12454
|
// src/server/embedded-lifecycle.ts
|
|
12208
|
-
import * as
|
|
12455
|
+
import * as path16 from "node:path";
|
|
12209
12456
|
|
|
12210
12457
|
// src/server/network-info.ts
|
|
12211
12458
|
import * as os2 from "node:os";
|
|
@@ -12271,7 +12518,7 @@ function registerWebuiInstance(p, deps2 = {}) {
|
|
|
12271
12518
|
httpPort: p.httpPort,
|
|
12272
12519
|
host: p.host,
|
|
12273
12520
|
projectRoot: p.projectRoot,
|
|
12274
|
-
projectName:
|
|
12521
|
+
projectName: path16.basename(p.projectRoot) || p.projectRoot,
|
|
12275
12522
|
startedAt: p.startedAt,
|
|
12276
12523
|
url: buildWebUIAccessUrl({
|
|
12277
12524
|
host: p.host,
|
|
@@ -12322,6 +12569,7 @@ function createWebuiShutdown(res) {
|
|
|
12322
12569
|
log("[WebUI] Shutting down...");
|
|
12323
12570
|
res.abortInFlight();
|
|
12324
12571
|
res.unsubscribeEvents();
|
|
12572
|
+
res.disposeResources?.();
|
|
12325
12573
|
res.closeClients();
|
|
12326
12574
|
const unregistered = unregister(res.pid, res.registryBaseDir).catch(
|
|
12327
12575
|
(err) => debug(`[webui-server] unregister failed: ${err}`)
|
|
@@ -12491,7 +12739,7 @@ ${text2}` : text2;
|
|
|
12491
12739
|
|
|
12492
12740
|
// src/server/client-presence.ts
|
|
12493
12741
|
import * as crypto2 from "node:crypto";
|
|
12494
|
-
import * as
|
|
12742
|
+
import * as path17 from "node:path";
|
|
12495
12743
|
import {
|
|
12496
12744
|
getSharedProjectMailbox as getSharedProjectMailbox2,
|
|
12497
12745
|
resolveProjectDir as resolveProjectDir2
|
|
@@ -12509,7 +12757,7 @@ function createWebuiClientPresence(deps2) {
|
|
|
12509
12757
|
if (!deps2.projectRoot) return null;
|
|
12510
12758
|
try {
|
|
12511
12759
|
const projectRoot = deps2.projectRoot;
|
|
12512
|
-
const projectName =
|
|
12760
|
+
const projectName = path17.basename(projectRoot);
|
|
12513
12761
|
const nextMailbox = getSharedProjectMailbox2(
|
|
12514
12762
|
resolveProjectDir2(projectRoot, wstackGlobalRoot()),
|
|
12515
12763
|
deps2.events,
|
|
@@ -13128,6 +13376,26 @@ function createKanbanSupervisor(deps2) {
|
|
|
13128
13376
|
const agentRunning = /* @__PURE__ */ new Set();
|
|
13129
13377
|
let disposed = false;
|
|
13130
13378
|
let nextTimer;
|
|
13379
|
+
const forgetBoard = (boardId) => {
|
|
13380
|
+
snapshots.delete(boardId);
|
|
13381
|
+
nextDue.delete(boardId);
|
|
13382
|
+
agentLastRun.delete(boardId);
|
|
13383
|
+
agentRunning.delete(boardId);
|
|
13384
|
+
};
|
|
13385
|
+
const pruneAbsentBoards = (presentBoardIds) => {
|
|
13386
|
+
for (const boardId of snapshots.keys()) {
|
|
13387
|
+
if (!presentBoardIds.has(boardId)) forgetBoard(boardId);
|
|
13388
|
+
}
|
|
13389
|
+
for (const boardId of nextDue.keys()) {
|
|
13390
|
+
if (!presentBoardIds.has(boardId)) forgetBoard(boardId);
|
|
13391
|
+
}
|
|
13392
|
+
for (const boardId of agentLastRun.keys()) {
|
|
13393
|
+
if (!presentBoardIds.has(boardId)) forgetBoard(boardId);
|
|
13394
|
+
}
|
|
13395
|
+
for (const boardId of agentRunning) {
|
|
13396
|
+
if (!presentBoardIds.has(boardId)) forgetBoard(boardId);
|
|
13397
|
+
}
|
|
13398
|
+
};
|
|
13131
13399
|
const publish = (snapshot) => {
|
|
13132
13400
|
snapshots.set(snapshot.boardId, snapshot);
|
|
13133
13401
|
deps2.broadcast({
|
|
@@ -13227,6 +13495,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
13227
13495
|
onDone: async (result) => {
|
|
13228
13496
|
clearTimeout(watchdog);
|
|
13229
13497
|
agentRunning.delete(board.id);
|
|
13498
|
+
if (await getBoard3(deps2.projectRoot, board.id) === null) return;
|
|
13230
13499
|
const current3 = snapshots.get(board.id) ?? snapshot;
|
|
13231
13500
|
publish({
|
|
13232
13501
|
...current3,
|
|
@@ -13248,13 +13517,20 @@ function createKanbanSupervisor(deps2) {
|
|
|
13248
13517
|
}
|
|
13249
13518
|
};
|
|
13250
13519
|
const auditNow = async (boardId) => {
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
(
|
|
13255
|
-
(
|
|
13256
|
-
|
|
13257
|
-
|
|
13520
|
+
let boards;
|
|
13521
|
+
if (boardId) {
|
|
13522
|
+
const board = await getBoard3(deps2.projectRoot, boardId);
|
|
13523
|
+
if (board === null) {
|
|
13524
|
+
forgetBoard(boardId);
|
|
13525
|
+
boards = [];
|
|
13526
|
+
} else {
|
|
13527
|
+
boards = [board];
|
|
13528
|
+
}
|
|
13529
|
+
} else {
|
|
13530
|
+
const summaries = await listBoards4(deps2.projectRoot);
|
|
13531
|
+
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
13532
|
+
boards = (await Promise.all(summaries.map((summary) => getBoard3(deps2.projectRoot, summary.id)))).filter((board) => Boolean(board));
|
|
13533
|
+
}
|
|
13258
13534
|
const results = [];
|
|
13259
13535
|
for (const board of boards) results.push(await auditBoard(board));
|
|
13260
13536
|
scheduleNext();
|
|
@@ -13289,6 +13565,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
13289
13565
|
try {
|
|
13290
13566
|
const now = Date.now();
|
|
13291
13567
|
const summaries = await listBoards4(deps2.projectRoot);
|
|
13568
|
+
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
13292
13569
|
for (const summary of summaries) {
|
|
13293
13570
|
if ((nextDue.get(summary.id) ?? 0) > now) continue;
|
|
13294
13571
|
const board = await getBoard3(deps2.projectRoot, summary.id);
|
|
@@ -13304,12 +13581,22 @@ function createKanbanSupervisor(deps2) {
|
|
|
13304
13581
|
return {
|
|
13305
13582
|
getSnapshot: (boardId) => snapshots.get(boardId),
|
|
13306
13583
|
auditNow,
|
|
13584
|
+
getStats: () => ({
|
|
13585
|
+
snapshots: snapshots.size,
|
|
13586
|
+
scheduledBoards: nextDue.size,
|
|
13587
|
+
agentCooldowns: agentLastRun.size,
|
|
13588
|
+
runningAgents: agentRunning.size
|
|
13589
|
+
}),
|
|
13307
13590
|
dispose() {
|
|
13308
13591
|
disposed = true;
|
|
13309
13592
|
if (nextTimer !== void 0) {
|
|
13310
13593
|
clearTimeout(nextTimer);
|
|
13311
13594
|
nextTimer = void 0;
|
|
13312
13595
|
}
|
|
13596
|
+
snapshots.clear();
|
|
13597
|
+
nextDue.clear();
|
|
13598
|
+
agentLastRun.clear();
|
|
13599
|
+
agentRunning.clear();
|
|
13313
13600
|
}
|
|
13314
13601
|
};
|
|
13315
13602
|
}
|
|
@@ -13486,8 +13773,8 @@ function seedContextMeta(config, context) {
|
|
|
13486
13773
|
}
|
|
13487
13774
|
|
|
13488
13775
|
// src/server/pref-helpers.ts
|
|
13489
|
-
import * as
|
|
13490
|
-
import * as
|
|
13776
|
+
import * as fs13 from "node:fs/promises";
|
|
13777
|
+
import * as path18 from "node:path";
|
|
13491
13778
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
13492
13779
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
13493
13780
|
var PREF_KEYS = [
|
|
@@ -13583,11 +13870,11 @@ function prefSnapshot(contextMeta) {
|
|
|
13583
13870
|
return snapshot;
|
|
13584
13871
|
}
|
|
13585
13872
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
13586
|
-
const globalRoot =
|
|
13873
|
+
const globalRoot = path18.dirname(filePath);
|
|
13587
13874
|
await backupConfigFile(filePath, { globalRoot });
|
|
13588
13875
|
let raw;
|
|
13589
13876
|
try {
|
|
13590
|
-
raw = await
|
|
13877
|
+
raw = await fs13.readFile(filePath, "utf8");
|
|
13591
13878
|
} catch {
|
|
13592
13879
|
raw = "{}";
|
|
13593
13880
|
}
|
|
@@ -14062,16 +14349,16 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
14062
14349
|
}
|
|
14063
14350
|
|
|
14064
14351
|
// src/server/embedded-host-adapters.ts
|
|
14065
|
-
import * as
|
|
14066
|
-
import * as
|
|
14352
|
+
import * as fs16 from "node:fs/promises";
|
|
14353
|
+
import * as path20 from "node:path";
|
|
14067
14354
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
14068
14355
|
import { DefaultSessionStore as DefaultSessionStore2 } from "@wrongstack/core/storage";
|
|
14069
14356
|
import { toErrorMessage as toErrorMessage7, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
|
|
14070
14357
|
import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
14071
14358
|
|
|
14072
14359
|
// src/server/project-handlers.ts
|
|
14073
|
-
import * as
|
|
14074
|
-
import * as
|
|
14360
|
+
import * as fs14 from "node:fs/promises";
|
|
14361
|
+
import * as path19 from "node:path";
|
|
14075
14362
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
14076
14363
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
14077
14364
|
function createProjectHandlers(ctx) {
|
|
@@ -14123,10 +14410,10 @@ function createProjectHandlers(ctx) {
|
|
|
14123
14410
|
});
|
|
14124
14411
|
return;
|
|
14125
14412
|
}
|
|
14126
|
-
const resolved =
|
|
14127
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14413
|
+
const resolved = path19.resolve(parsed.value.root);
|
|
14414
|
+
const name2 = parsed.value.name?.trim() || path19.basename(resolved);
|
|
14128
14415
|
try {
|
|
14129
|
-
const stat3 = await
|
|
14416
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
14130
14417
|
if (!stat3?.isDirectory()) {
|
|
14131
14418
|
sendTo(ws, {
|
|
14132
14419
|
type: "projects.added",
|
|
@@ -14135,7 +14422,7 @@ function createProjectHandlers(ctx) {
|
|
|
14135
14422
|
return;
|
|
14136
14423
|
}
|
|
14137
14424
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
14138
|
-
const already = before.projects.some((project) =>
|
|
14425
|
+
const already = before.projects.some((project) => path19.resolve(project.root) === resolved);
|
|
14139
14426
|
const entry = await touchProjectInManifest(
|
|
14140
14427
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
14141
14428
|
ctx.globalConfigPath
|
|
@@ -14165,8 +14452,8 @@ function createProjectHandlers(ctx) {
|
|
|
14165
14452
|
});
|
|
14166
14453
|
return;
|
|
14167
14454
|
}
|
|
14168
|
-
const resolved =
|
|
14169
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14455
|
+
const resolved = path19.resolve(parsed.value.root);
|
|
14456
|
+
const name2 = parsed.value.name?.trim() || path19.basename(resolved);
|
|
14170
14457
|
if (!ctx.allowProjectMutations) {
|
|
14171
14458
|
sendTo(ws, {
|
|
14172
14459
|
type: "projects.selected",
|
|
@@ -14179,7 +14466,7 @@ function createProjectHandlers(ctx) {
|
|
|
14179
14466
|
return;
|
|
14180
14467
|
}
|
|
14181
14468
|
try {
|
|
14182
|
-
const stat3 = await
|
|
14469
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
14183
14470
|
if (!stat3?.isDirectory()) {
|
|
14184
14471
|
sendTo(ws, {
|
|
14185
14472
|
type: "projects.selected",
|
|
@@ -14209,7 +14496,7 @@ function createProjectHandlers(ctx) {
|
|
|
14209
14496
|
const previousIdentityTarget = {
|
|
14210
14497
|
projectSlug: previousPaths.projectSlug,
|
|
14211
14498
|
projectRoot: previousProjectRoot,
|
|
14212
|
-
projectName:
|
|
14499
|
+
projectName: path19.basename(previousProjectRoot),
|
|
14213
14500
|
workingDir: ctx.context.workingDir
|
|
14214
14501
|
};
|
|
14215
14502
|
const previousUsage = ctx.tokenCounter.total();
|
|
@@ -14372,14 +14659,14 @@ async function searchCatalogModels(registry, rawQuery, limit = 8) {
|
|
|
14372
14659
|
}
|
|
14373
14660
|
|
|
14374
14661
|
// src/server/provider-config-io.ts
|
|
14375
|
-
import * as
|
|
14662
|
+
import * as fs15 from "node:fs/promises";
|
|
14376
14663
|
import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
|
|
14377
14664
|
import { atomicWrite as atomicWrite7 } from "@wrongstack/core/utils";
|
|
14378
14665
|
import { decryptConfigSecrets as decryptConfigSecrets3, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
14379
14666
|
async function loadSavedProviders(configPath, vault) {
|
|
14380
14667
|
let raw;
|
|
14381
14668
|
try {
|
|
14382
|
-
raw = await
|
|
14669
|
+
raw = await fs15.readFile(configPath, "utf8");
|
|
14383
14670
|
} catch {
|
|
14384
14671
|
return {};
|
|
14385
14672
|
}
|
|
@@ -14397,7 +14684,7 @@ async function saveProviders(configPath, vault, providers, profileConfigPath) {
|
|
|
14397
14684
|
let raw;
|
|
14398
14685
|
let fileExists = true;
|
|
14399
14686
|
try {
|
|
14400
|
-
raw = await
|
|
14687
|
+
raw = await fs15.readFile(targetPath, "utf8");
|
|
14401
14688
|
} catch (err) {
|
|
14402
14689
|
if (err.code !== "ENOENT") {
|
|
14403
14690
|
throw new ConfigError2({
|
|
@@ -15163,6 +15450,7 @@ var CLIENT_COLLABORATION_MESSAGE_TYPES = [
|
|
|
15163
15450
|
"collab.resume",
|
|
15164
15451
|
"collab.grant_control",
|
|
15165
15452
|
"collab.inject_tool",
|
|
15453
|
+
"mailbox.action",
|
|
15166
15454
|
"mailbox.agents",
|
|
15167
15455
|
"mailbox.clear",
|
|
15168
15456
|
"mailbox.compact",
|
|
@@ -15428,6 +15716,7 @@ var SERVER_COLLABORATION_MESSAGE_TYPES = [
|
|
|
15428
15716
|
"collab.pause.granted",
|
|
15429
15717
|
"collab.pause.released",
|
|
15430
15718
|
"collab.state",
|
|
15719
|
+
"mailbox.action_result",
|
|
15431
15720
|
"mailbox.agent_registered",
|
|
15432
15721
|
"mailbox.agents",
|
|
15433
15722
|
"mailbox.cleared",
|
|
@@ -15651,13 +15940,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
15651
15940
|
// src/protocol/decoder.ts
|
|
15652
15941
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
15653
15942
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
15654
|
-
function inspectValue(value,
|
|
15943
|
+
function inspectValue(value, path35, depth) {
|
|
15655
15944
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
15656
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
15945
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path35 };
|
|
15657
15946
|
}
|
|
15658
15947
|
if (value === null || typeof value !== "object") return null;
|
|
15659
15948
|
for (const key of Object.keys(value)) {
|
|
15660
|
-
const childPath = `${
|
|
15949
|
+
const childPath = `${path35}.${key}`;
|
|
15661
15950
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
15662
15951
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
15663
15952
|
}
|
|
@@ -15754,7 +16043,11 @@ function projectChatMessage(message) {
|
|
|
15754
16043
|
return value ? { kind: "text-delta", text: value, messageId: text(payload["messageId"]) } : null;
|
|
15755
16044
|
}
|
|
15756
16045
|
case "provider.response":
|
|
15757
|
-
return {
|
|
16046
|
+
return {
|
|
16047
|
+
kind: "response",
|
|
16048
|
+
content: payload["content"],
|
|
16049
|
+
stopReason: text(payload["stopReason"])
|
|
16050
|
+
};
|
|
15758
16051
|
case "run.result":
|
|
15759
16052
|
return {
|
|
15760
16053
|
kind: "run-result",
|
|
@@ -16591,7 +16884,7 @@ function createEmbeddedConversationRoutes(ctx) {
|
|
|
16591
16884
|
}
|
|
16592
16885
|
function sessionStoreFor(opts) {
|
|
16593
16886
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
16594
|
-
return opts.sessionStore ?? new DefaultSessionStore2({ dir:
|
|
16887
|
+
return opts.sessionStore ?? new DefaultSessionStore2({ dir: path20.join(projectRoot, ".wrongstack", "sessions"), projectRoot });
|
|
16595
16888
|
}
|
|
16596
16889
|
function createEmbeddedSessionRoutes(ctx) {
|
|
16597
16890
|
const { opts } = ctx;
|
|
@@ -16610,7 +16903,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16610
16903
|
getSession: () => actx.session ?? opts.session,
|
|
16611
16904
|
getSessionStore: () => sessionStoreFor(opts),
|
|
16612
16905
|
canSwapSessions: () => opts.sessionStore !== void 0,
|
|
16613
|
-
getSessionsDir: () => opts.sessionsDir ??
|
|
16906
|
+
getSessionsDir: () => opts.sessionsDir ?? path20.join(getProjectRoot(), ".wrongstack", "sessions"),
|
|
16614
16907
|
setSession: (next) => {
|
|
16615
16908
|
actx.session = next;
|
|
16616
16909
|
},
|
|
@@ -16627,7 +16920,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16627
16920
|
async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
16628
16921
|
const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
16629
16922
|
try {
|
|
16630
|
-
const raw = await
|
|
16923
|
+
const raw = await fs16.readFile(path20.join(projectRoot, ".wrongstack", "goal.json"), "utf8");
|
|
16631
16924
|
ctx.broadcast({ type: "goal-state.updated", payload: JSON.parse(raw) });
|
|
16632
16925
|
} catch {
|
|
16633
16926
|
ctx.broadcast({ type: "goal-state.updated", payload: null });
|
|
@@ -16636,10 +16929,10 @@ async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
|
16636
16929
|
function createEmbeddedProjectRoutes(ctx) {
|
|
16637
16930
|
const { opts } = ctx;
|
|
16638
16931
|
const actx = opts.agent.ctx;
|
|
16639
|
-
const globalConfigPath = opts.globalConfigPath ??
|
|
16932
|
+
const globalConfigPath = opts.globalConfigPath ?? path20.join(wstackGlobalRoot2(), "config.json");
|
|
16640
16933
|
return createProjectHandlers({
|
|
16641
16934
|
globalConfigPath,
|
|
16642
|
-
wpaths: { globalRoot:
|
|
16935
|
+
wpaths: { globalRoot: path20.dirname(globalConfigPath) },
|
|
16643
16936
|
context: actx,
|
|
16644
16937
|
tokenCounter: actx.tokenCounter,
|
|
16645
16938
|
config: { model: actx.model, provider: actx.provider.id },
|
|
@@ -17687,8 +17980,8 @@ function createRouteFamilyDispatcher(options) {
|
|
|
17687
17980
|
}
|
|
17688
17981
|
|
|
17689
17982
|
// src/server/shell-open.ts
|
|
17690
|
-
import * as
|
|
17691
|
-
import * as
|
|
17983
|
+
import * as fs17 from "node:fs/promises";
|
|
17984
|
+
import * as path21 from "node:path";
|
|
17692
17985
|
import { spawn as spawn3 } from "node:child_process";
|
|
17693
17986
|
function normalizeShellOpenTarget(target) {
|
|
17694
17987
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -17699,11 +17992,11 @@ function shellQuote(s) {
|
|
|
17699
17992
|
}
|
|
17700
17993
|
async function handleShellOpen(req, logger, options) {
|
|
17701
17994
|
try {
|
|
17702
|
-
const resolved =
|
|
17995
|
+
const resolved = path21.resolve(req.path);
|
|
17703
17996
|
if (options?.projectRoot) {
|
|
17704
|
-
const root =
|
|
17705
|
-
const relative5 =
|
|
17706
|
-
const escapes = relative5.startsWith("..") ||
|
|
17997
|
+
const root = path21.resolve(options.projectRoot);
|
|
17998
|
+
const relative5 = path21.relative(root, resolved);
|
|
17999
|
+
const escapes = relative5.startsWith("..") || path21.isAbsolute(relative5);
|
|
17707
18000
|
if (escapes) {
|
|
17708
18001
|
return {
|
|
17709
18002
|
success: false,
|
|
@@ -17711,7 +18004,7 @@ async function handleShellOpen(req, logger, options) {
|
|
|
17711
18004
|
};
|
|
17712
18005
|
}
|
|
17713
18006
|
}
|
|
17714
|
-
await
|
|
18007
|
+
await fs17.access(resolved);
|
|
17715
18008
|
if (METACHAR_REGEX.test(resolved)) {
|
|
17716
18009
|
return { success: false, message: "Path contains unsupported characters." };
|
|
17717
18010
|
}
|
|
@@ -18115,7 +18408,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
18115
18408
|
}
|
|
18116
18409
|
|
|
18117
18410
|
// src/server/provider-config-standalone.ts
|
|
18118
|
-
import * as
|
|
18411
|
+
import * as path22 from "node:path";
|
|
18119
18412
|
import { DefaultSecretVault } from "@wrongstack/core/security";
|
|
18120
18413
|
function createProviderConfigIO(configPath) {
|
|
18121
18414
|
const keyFile = vaultKeyFileForConfigPath(configPath);
|
|
@@ -18126,15 +18419,15 @@ function createProviderConfigIO(configPath) {
|
|
|
18126
18419
|
};
|
|
18127
18420
|
}
|
|
18128
18421
|
function vaultKeyFileForConfigPath(configPath) {
|
|
18129
|
-
const configDir =
|
|
18130
|
-
const parentDir =
|
|
18131
|
-
const globalRoot =
|
|
18132
|
-
return
|
|
18422
|
+
const configDir = path22.dirname(configPath);
|
|
18423
|
+
const parentDir = path22.dirname(configDir);
|
|
18424
|
+
const globalRoot = path22.basename(parentDir) === "profiles" ? path22.dirname(parentDir) : configDir;
|
|
18425
|
+
return path22.join(globalRoot, ".key");
|
|
18133
18426
|
}
|
|
18134
18427
|
|
|
18135
18428
|
// src/server/provider-store.ts
|
|
18136
18429
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
18137
|
-
import * as
|
|
18430
|
+
import * as fs18 from "node:fs/promises";
|
|
18138
18431
|
import { decryptConfigSecrets as decryptConfigSecrets4, encryptConfigSecrets as encryptConfigSecrets3 } from "@wrongstack/core/security";
|
|
18139
18432
|
import { atomicWrite as atomicWrite8 } from "@wrongstack/core/utils";
|
|
18140
18433
|
function createConfigWriteLock() {
|
|
@@ -18158,7 +18451,7 @@ function createProviderStore(deps2) {
|
|
|
18158
18451
|
const configWriteLock = createConfigWriteLock();
|
|
18159
18452
|
async function loadSavedProviders2() {
|
|
18160
18453
|
try {
|
|
18161
|
-
const raw = await
|
|
18454
|
+
const raw = await fs18.readFile(profileConfigPath, "utf8");
|
|
18162
18455
|
const parsed = JSON.parse(raw);
|
|
18163
18456
|
if (!parsed.providers) return {};
|
|
18164
18457
|
return decryptConfigSecrets4(parsed.providers, vault);
|
|
@@ -18172,7 +18465,7 @@ function createProviderStore(deps2) {
|
|
|
18172
18465
|
await prev;
|
|
18173
18466
|
let parsed;
|
|
18174
18467
|
try {
|
|
18175
|
-
const raw = await
|
|
18468
|
+
const raw = await fs18.readFile(profileConfigPath, "utf8");
|
|
18176
18469
|
parsed = JSON.parse(raw);
|
|
18177
18470
|
} catch {
|
|
18178
18471
|
parsed = {};
|
|
@@ -18222,7 +18515,12 @@ function createProviderStore(deps2) {
|
|
|
18222
18515
|
}
|
|
18223
18516
|
|
|
18224
18517
|
// src/server/sdd-board-ws-handler.ts
|
|
18225
|
-
import {
|
|
18518
|
+
import {
|
|
18519
|
+
enqueueKanbanWorkflowCommand,
|
|
18520
|
+
kanbanWorkflowId,
|
|
18521
|
+
listBoards as listBoards5,
|
|
18522
|
+
listKanbanWorkflowStates
|
|
18523
|
+
} from "@wrongstack/kanban";
|
|
18226
18524
|
import {
|
|
18227
18525
|
applySddLifecycle,
|
|
18228
18526
|
extractVerificationCommand,
|
|
@@ -18249,7 +18547,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18249
18547
|
clients = /* @__PURE__ */ new Set();
|
|
18250
18548
|
lifecycle;
|
|
18251
18549
|
security;
|
|
18252
|
-
|
|
18550
|
+
standalonePollingEnabled;
|
|
18253
18551
|
latest = null;
|
|
18254
18552
|
poll = null;
|
|
18255
18553
|
pollInFlight = false;
|
|
@@ -18258,7 +18556,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18258
18556
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
18259
18557
|
this.lifecycle = lifecycle;
|
|
18260
18558
|
this.security = security;
|
|
18261
|
-
this.
|
|
18559
|
+
this.standalonePollingEnabled = events === void 0;
|
|
18262
18560
|
if (events) {
|
|
18263
18561
|
const handler = (e) => {
|
|
18264
18562
|
this.latest = e.snapshot;
|
|
@@ -18293,7 +18591,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18293
18591
|
return;
|
|
18294
18592
|
}
|
|
18295
18593
|
if (msg.type === "sdd.board.list") {
|
|
18296
|
-
const boards = await this.
|
|
18594
|
+
const boards = await this.listBoardEntries();
|
|
18297
18595
|
this.broadcast({ type: "sdd.board.list", payload: { boards } });
|
|
18298
18596
|
return;
|
|
18299
18597
|
}
|
|
@@ -18306,7 +18604,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
18306
18604
|
const verificationCommands = [];
|
|
18307
18605
|
if (action === "set_task_verification") {
|
|
18308
18606
|
const command = msg.payload?.verificationCommand;
|
|
18309
|
-
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
18607
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
18608
|
+
return;
|
|
18310
18609
|
if (typeof command === "string" && command.trim()) {
|
|
18311
18610
|
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
18312
18611
|
}
|
|
@@ -18340,18 +18639,26 @@ var SddBoardWebSocketHandler = class {
|
|
|
18340
18639
|
);
|
|
18341
18640
|
if (!authorization.allowed) return;
|
|
18342
18641
|
}
|
|
18343
|
-
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.
|
|
18642
|
+
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.listBoardEntries())[0]?.runId;
|
|
18344
18643
|
if (runId) {
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18644
|
+
if (this.lifecycle && this.lifecycle.controlTransport !== "legacy-file") {
|
|
18645
|
+
await enqueueKanbanWorkflowCommand(
|
|
18646
|
+
this.lifecycle.projectRoot,
|
|
18647
|
+
kanbanWorkflowId("sdd", runId),
|
|
18648
|
+
{ type: action, payload: msg.payload }
|
|
18649
|
+
);
|
|
18650
|
+
} else {
|
|
18651
|
+
await this.store.appendControl(runId, {
|
|
18652
|
+
ts: Date.now(),
|
|
18653
|
+
type: action,
|
|
18654
|
+
payload: msg.payload
|
|
18655
|
+
});
|
|
18656
|
+
}
|
|
18350
18657
|
}
|
|
18351
18658
|
}
|
|
18352
18659
|
}
|
|
18353
18660
|
/**
|
|
18354
|
-
* Apply a cleanup/rollback/destroy from
|
|
18661
|
+
* Apply a cleanup/rollback/destroy from durable state and broadcast a structured
|
|
18355
18662
|
* `sdd.board.lifecycle_result`. Refuses (no-op) while a run is still active —
|
|
18356
18663
|
* the user must stop it first; the UI gates the buttons on `!active` and the
|
|
18357
18664
|
* Destroy flow auto-stops then waits before sending `destroy`.
|
|
@@ -18360,7 +18667,11 @@ var SddBoardWebSocketHandler = class {
|
|
|
18360
18667
|
if (!this.lifecycle) {
|
|
18361
18668
|
this.broadcast({
|
|
18362
18669
|
type: "sdd.board.lifecycle_result",
|
|
18363
|
-
payload: {
|
|
18670
|
+
payload: {
|
|
18671
|
+
op,
|
|
18672
|
+
ok: false,
|
|
18673
|
+
reason: "Lifecycle operations are not available in this session."
|
|
18674
|
+
}
|
|
18364
18675
|
});
|
|
18365
18676
|
return;
|
|
18366
18677
|
}
|
|
@@ -18376,6 +18687,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18376
18687
|
projectRoot: this.lifecycle.projectRoot,
|
|
18377
18688
|
paths: this.lifecycle.paths,
|
|
18378
18689
|
runId,
|
|
18690
|
+
stateTransport: this.lifecycle.stateTransport ?? "kanban",
|
|
18379
18691
|
revertMerged: payload?.revertMerged === true
|
|
18380
18692
|
});
|
|
18381
18693
|
this.broadcast({ type: "sdd.board.lifecycle_result", payload: result });
|
|
@@ -18404,22 +18716,27 @@ var SddBoardWebSocketHandler = class {
|
|
|
18404
18716
|
if (this.pollInFlight) return;
|
|
18405
18717
|
this.pollInFlight = true;
|
|
18406
18718
|
try {
|
|
18407
|
-
const
|
|
18408
|
-
if (!
|
|
18409
|
-
if (this.latest && this.latest.updatedAt >=
|
|
18719
|
+
const snap = await this.loadLatestSnapshot();
|
|
18720
|
+
if (!snap) return;
|
|
18721
|
+
if (this.latest && this.latest.updatedAt >= snap.updatedAt && this.latest.runId === snap.runId) {
|
|
18410
18722
|
return;
|
|
18411
18723
|
}
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18415
|
-
|
|
18416
|
-
|
|
18724
|
+
this.latest = snap;
|
|
18725
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
18726
|
+
} catch (err) {
|
|
18727
|
+
console.warn(
|
|
18728
|
+
JSON.stringify({
|
|
18729
|
+
level: "warn",
|
|
18730
|
+
event: "sdd_board.poll_failed",
|
|
18731
|
+
message: err instanceof Error ? err.message : String(err)
|
|
18732
|
+
})
|
|
18733
|
+
);
|
|
18417
18734
|
} finally {
|
|
18418
18735
|
this.pollInFlight = false;
|
|
18419
18736
|
}
|
|
18420
18737
|
}
|
|
18421
18738
|
startPolling() {
|
|
18422
|
-
if (!this.
|
|
18739
|
+
if (!this.standalonePollingEnabled || this.poll !== null || this.clients.size === 0) return;
|
|
18423
18740
|
this.poll = setInterval(() => void this.pollLatest(), 1e3);
|
|
18424
18741
|
this.poll.unref?.();
|
|
18425
18742
|
}
|
|
@@ -18429,17 +18746,44 @@ var SddBoardWebSocketHandler = class {
|
|
|
18429
18746
|
this.poll = null;
|
|
18430
18747
|
}
|
|
18431
18748
|
async sendCurrent(client) {
|
|
18432
|
-
const snap = this.latest ?? await this.
|
|
18749
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
18433
18750
|
if (snap) this.send(client, { type: "sdd.board.snapshot", payload: snap });
|
|
18434
18751
|
}
|
|
18435
18752
|
async broadcastCurrent() {
|
|
18436
|
-
const snap = this.latest ?? await this.
|
|
18753
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
18437
18754
|
if (snap) this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
18438
18755
|
}
|
|
18439
|
-
async
|
|
18756
|
+
async loadLatestSnapshot() {
|
|
18757
|
+
if (this.usesKanbanState()) {
|
|
18758
|
+
const states = await listKanbanWorkflowStates(this.lifecycle.projectRoot, "sdd:");
|
|
18759
|
+
const snapshots = states.map((state) => state.value).filter(isSddBoardSnapshot).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
18760
|
+
return snapshots[0] ?? null;
|
|
18761
|
+
}
|
|
18440
18762
|
const entry = await this.store.latest();
|
|
18441
18763
|
return entry ? this.store.load(entry.runId) : null;
|
|
18442
18764
|
}
|
|
18765
|
+
async listBoardEntries() {
|
|
18766
|
+
if (!this.usesKanbanState()) return this.store.list();
|
|
18767
|
+
const states = await listKanbanWorkflowStates(this.lifecycle.projectRoot, "sdd:");
|
|
18768
|
+
return states.flatMap((state) => {
|
|
18769
|
+
if (!isSddBoardSnapshot(state.value)) return [];
|
|
18770
|
+
const snapshot = state.value;
|
|
18771
|
+
return [
|
|
18772
|
+
{
|
|
18773
|
+
runId: snapshot.runId,
|
|
18774
|
+
...snapshot.specId ? { specId: snapshot.specId } : {},
|
|
18775
|
+
title: snapshot.title,
|
|
18776
|
+
status: snapshot.status,
|
|
18777
|
+
total: snapshot.progress.total,
|
|
18778
|
+
completed: snapshot.progress.completed,
|
|
18779
|
+
updatedAt: snapshot.updatedAt
|
|
18780
|
+
}
|
|
18781
|
+
];
|
|
18782
|
+
});
|
|
18783
|
+
}
|
|
18784
|
+
usesKanbanState() {
|
|
18785
|
+
return Boolean(this.lifecycle && this.lifecycle.stateTransport !== "legacy-file");
|
|
18786
|
+
}
|
|
18443
18787
|
broadcast(msg) {
|
|
18444
18788
|
const data = JSON.stringify(msg);
|
|
18445
18789
|
for (const client of this.clients) {
|
|
@@ -18450,17 +18794,20 @@ var SddBoardWebSocketHandler = class {
|
|
|
18450
18794
|
sendSerialized(client.ws, JSON.stringify(msg));
|
|
18451
18795
|
}
|
|
18452
18796
|
};
|
|
18797
|
+
function isSddBoardSnapshot(value) {
|
|
18798
|
+
if (!value || typeof value !== "object") return false;
|
|
18799
|
+
const snapshot = value;
|
|
18800
|
+
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");
|
|
18801
|
+
}
|
|
18453
18802
|
|
|
18454
18803
|
// src/server/sdd-wizard-wiring.ts
|
|
18455
|
-
import * as
|
|
18456
|
-
import {
|
|
18457
|
-
DefaultTaskStore,
|
|
18458
|
-
TaskTracker
|
|
18459
|
-
} from "@wrongstack/core/tasking";
|
|
18804
|
+
import * as path23 from "node:path";
|
|
18805
|
+
import { DefaultTaskStore, TaskTracker } from "@wrongstack/core/tasking";
|
|
18460
18806
|
import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
18461
18807
|
import { WorktreeManager as WorktreeManager2 } from "@wrongstack/core/worktree";
|
|
18462
18808
|
import {
|
|
18463
18809
|
cleanupStaleSddWorktrees,
|
|
18810
|
+
createKanbanSddSessionPersistence,
|
|
18464
18811
|
decomposeNonAtomicTasks,
|
|
18465
18812
|
gatherProjectContext,
|
|
18466
18813
|
makeAcceptanceCriteriaVerifier,
|
|
@@ -18478,7 +18825,7 @@ import {
|
|
|
18478
18825
|
} from "@wrongstack/sdd";
|
|
18479
18826
|
async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
18480
18827
|
const runTracker = tracker ?? (() => {
|
|
18481
|
-
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
18828
|
+
const t = new TaskTracker({ store: deps2.taskStore ?? new DefaultTaskStore() });
|
|
18482
18829
|
t.setGraph(graph);
|
|
18483
18830
|
return t;
|
|
18484
18831
|
})();
|
|
@@ -18487,7 +18834,8 @@ async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
|
18487
18834
|
if (worktreesEnabled && await isGitWorkTree(deps2.projectRoot)) {
|
|
18488
18835
|
void cleanupStaleSddWorktrees({
|
|
18489
18836
|
projectRoot: deps2.projectRoot,
|
|
18490
|
-
boardsDir: deps2.projectSddBoards
|
|
18837
|
+
boardsDir: deps2.projectSddBoards,
|
|
18838
|
+
stateTransport: "kanban"
|
|
18491
18839
|
}).catch(() => void 0);
|
|
18492
18840
|
worktrees = new WorktreeManager2({
|
|
18493
18841
|
projectRoot: deps2.projectRoot,
|
|
@@ -18566,7 +18914,8 @@ function buildSddWizardDeps(opts) {
|
|
|
18566
18914
|
}).catch(() => {
|
|
18567
18915
|
projectContext = "";
|
|
18568
18916
|
});
|
|
18569
|
-
const
|
|
18917
|
+
const legacySessionPath = opts.paths.projectSddSession ?? path23.join(opts.paths.projectDir, "sdd-session.json");
|
|
18918
|
+
const sessionPersistence = createKanbanSddSessionPersistence(opts.projectRoot, legacySessionPath);
|
|
18570
18919
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
18571
18920
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
18572
18921
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -18587,7 +18936,7 @@ function buildSddWizardDeps(opts) {
|
|
|
18587
18936
|
const makeDriver = () => new SddInterviewDriver({
|
|
18588
18937
|
specStore,
|
|
18589
18938
|
graphStore,
|
|
18590
|
-
|
|
18939
|
+
sessionPersistence,
|
|
18591
18940
|
projectContext
|
|
18592
18941
|
});
|
|
18593
18942
|
const launchFromGraph = async (graph, config, tracker) => {
|
|
@@ -18599,6 +18948,7 @@ function buildSddWizardDeps(opts) {
|
|
|
18599
18948
|
projectRoot: opts.projectRoot,
|
|
18600
18949
|
subagentFactory: opts.subagentFactory,
|
|
18601
18950
|
projectSddBoards: opts.paths.projectSddBoards,
|
|
18951
|
+
taskStore: graphStore,
|
|
18602
18952
|
registry,
|
|
18603
18953
|
runIsolatedTurn,
|
|
18604
18954
|
...opts.brain ? { brain: opts.brain } : {}
|
|
@@ -18668,8 +19018,18 @@ var SddWizardWebSocketHandler = class {
|
|
|
18668
19018
|
lastAgentText = "";
|
|
18669
19019
|
/** Guards against overlapping interview turns (one in flight at a time). */
|
|
18670
19020
|
busy = false;
|
|
18671
|
-
/**
|
|
19021
|
+
/** Set when authoritative session state could not be read during bootstrap. */
|
|
19022
|
+
resumeError = null;
|
|
19023
|
+
/** Resolves once project-context gather + durable resume finish. */
|
|
18672
19024
|
ready;
|
|
19025
|
+
/**
|
|
19026
|
+
* Single-flight slot for the resume probe. `handleMessage` awaits this
|
|
19027
|
+
* when `resumeError` is set, so concurrent frames cannot race the retry
|
|
19028
|
+
* — one probe either clears the gate or re-latches it, then every
|
|
19029
|
+
* queued frame re-checks and proceeds. `null` when no probe is in
|
|
19030
|
+
* flight (or after the probe has settled).
|
|
19031
|
+
*/
|
|
19032
|
+
resumeProbe = null;
|
|
18673
19033
|
async bootstrap() {
|
|
18674
19034
|
try {
|
|
18675
19035
|
await this.deps.ensureReady?.();
|
|
@@ -18677,7 +19037,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
18677
19037
|
}
|
|
18678
19038
|
await this.tryResume();
|
|
18679
19039
|
}
|
|
18680
|
-
/** Rehydrate a persisted interview if one exists
|
|
19040
|
+
/** Rehydrate a persisted interview if one exists. */
|
|
18681
19041
|
async tryResume() {
|
|
18682
19042
|
if (this.driver) return;
|
|
18683
19043
|
try {
|
|
@@ -18686,9 +19046,11 @@ var SddWizardWebSocketHandler = class {
|
|
|
18686
19046
|
this.driver = driver;
|
|
18687
19047
|
this.lastAgentText = driver.getLastAgentText() ?? "";
|
|
18688
19048
|
}
|
|
18689
|
-
|
|
19049
|
+
this.resumeError = null;
|
|
19050
|
+
} catch (error2) {
|
|
18690
19051
|
this.driver = null;
|
|
18691
19052
|
this.lastAgentText = "";
|
|
19053
|
+
this.resumeError = error2 instanceof Error ? error2.message : String(error2);
|
|
18692
19054
|
}
|
|
18693
19055
|
}
|
|
18694
19056
|
addClient(ws) {
|
|
@@ -18697,6 +19059,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
18697
19059
|
ws.on("close", () => this.clients.delete(client));
|
|
18698
19060
|
ws.on("error", () => this.clients.delete(client));
|
|
18699
19061
|
void this.ready.then(() => {
|
|
19062
|
+
if (this.resumeError) {
|
|
19063
|
+
this.send(client, { type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
19064
|
+
return;
|
|
19065
|
+
}
|
|
18700
19066
|
if (this.driver) {
|
|
18701
19067
|
this.send(client, this.snapshotMsg());
|
|
18702
19068
|
if (this.lastAgentText) {
|
|
@@ -18708,6 +19074,22 @@ var SddWizardWebSocketHandler = class {
|
|
|
18708
19074
|
async handleMessage(msg) {
|
|
18709
19075
|
try {
|
|
18710
19076
|
await this.ready;
|
|
19077
|
+
if (this.resumeError) {
|
|
19078
|
+
if (this.resumeProbe === null) {
|
|
19079
|
+
const probe = Promise.resolve().then(() => this.tryResume());
|
|
19080
|
+
this.resumeProbe = probe;
|
|
19081
|
+
void probe.finally(() => {
|
|
19082
|
+
setTimeout(() => {
|
|
19083
|
+
if (this.resumeProbe === probe) this.resumeProbe = null;
|
|
19084
|
+
}, 0);
|
|
19085
|
+
});
|
|
19086
|
+
}
|
|
19087
|
+
await this.resumeProbe;
|
|
19088
|
+
if (this.resumeError) {
|
|
19089
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
19090
|
+
return;
|
|
19091
|
+
}
|
|
19092
|
+
}
|
|
18711
19093
|
switch (msg.type) {
|
|
18712
19094
|
case "sdd.spec.start":
|
|
18713
19095
|
await this.onStart(String(msg.payload?.goal ?? "").trim(), {
|
|
@@ -18724,7 +19106,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
18724
19106
|
if (this.driver) {
|
|
18725
19107
|
this.broadcast(this.snapshotMsg());
|
|
18726
19108
|
if (this.lastAgentText) {
|
|
18727
|
-
this.broadcast({
|
|
19109
|
+
this.broadcast({
|
|
19110
|
+
type: "sdd.spec.agent_text",
|
|
19111
|
+
payload: { text: this.lastAgentText }
|
|
19112
|
+
});
|
|
18728
19113
|
}
|
|
18729
19114
|
}
|
|
18730
19115
|
break;
|
|
@@ -18938,10 +19323,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
18938
19323
|
|
|
18939
19324
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
18940
19325
|
import { watch as fsWatch } from "node:fs";
|
|
18941
|
-
import * as
|
|
19326
|
+
import * as path24 from "node:path";
|
|
18942
19327
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
18943
19328
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
18944
|
-
const globalRoot = globalConfigPath ?
|
|
19329
|
+
const globalRoot = globalConfigPath ? path24.dirname(globalConfigPath) : void 0;
|
|
18945
19330
|
if (!globalRoot) return void 0;
|
|
18946
19331
|
const disposers = [];
|
|
18947
19332
|
const broadcastSessions = async () => {
|
|
@@ -18951,8 +19336,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
18951
19336
|
const sessions = await registry.list();
|
|
18952
19337
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
18953
19338
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
18954
|
-
const myRoot =
|
|
18955
|
-
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug :
|
|
19339
|
+
const myRoot = path24.resolve(context.projectRoot);
|
|
19340
|
+
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path24.resolve(s.projectRoot) === myRoot).map((s) => ({
|
|
18956
19341
|
sessionId: s.sessionId,
|
|
18957
19342
|
projectName: s.projectName,
|
|
18958
19343
|
projectSlug: s.projectSlug,
|
|
@@ -19142,14 +19527,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
19142
19527
|
|
|
19143
19528
|
// src/server/setup-events-status-watcher.ts
|
|
19144
19529
|
import { watch as fsWatch2 } from "node:fs";
|
|
19145
|
-
import * as
|
|
19146
|
-
import * as
|
|
19530
|
+
import * as fs19 from "node:fs/promises";
|
|
19531
|
+
import * as path26 from "node:path";
|
|
19147
19532
|
|
|
19148
19533
|
// src/server/setup-events-watcher.ts
|
|
19149
|
-
import * as
|
|
19534
|
+
import * as path25 from "node:path";
|
|
19150
19535
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
19151
19536
|
const raw = String(filename);
|
|
19152
|
-
const relative5 =
|
|
19537
|
+
const relative5 = path25.isAbsolute(raw) ? path25.relative(projectsDir, raw) : raw;
|
|
19153
19538
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
19154
19539
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
19155
19540
|
return parts.at(-2) ?? null;
|
|
@@ -19184,7 +19569,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
19184
19569
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
19185
19570
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
19186
19571
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
19187
|
-
const projectsDir =
|
|
19572
|
+
const projectsDir = path26.join(wpaths.globalRoot, "projects");
|
|
19188
19573
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
19189
19574
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
19190
19575
|
const DEBOUNCE_MS2 = 150;
|
|
@@ -19229,7 +19614,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19229
19614
|
let watcher;
|
|
19230
19615
|
const startWatcher = async () => {
|
|
19231
19616
|
try {
|
|
19232
|
-
await
|
|
19617
|
+
await fs19.mkdir(projectsDir, { recursive: true });
|
|
19233
19618
|
if (isDisposed()) return;
|
|
19234
19619
|
watcher = fsWatch2(
|
|
19235
19620
|
projectsDir,
|
|
@@ -19243,8 +19628,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19243
19628
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
19244
19629
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
19245
19630
|
try {
|
|
19246
|
-
const targetFile =
|
|
19247
|
-
const content = await
|
|
19631
|
+
const targetFile = path26.join(projectsDir, projectHash, "status.json");
|
|
19632
|
+
const content = await fs19.readFile(targetFile, "utf-8");
|
|
19248
19633
|
const statusData = JSON.parse(content);
|
|
19249
19634
|
scheduleBroadcast(projectHash, statusData);
|
|
19250
19635
|
} catch {
|
|
@@ -19301,8 +19686,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19301
19686
|
}
|
|
19302
19687
|
|
|
19303
19688
|
// src/server/setup-events-core-watchers.ts
|
|
19304
|
-
import * as
|
|
19305
|
-
import * as
|
|
19689
|
+
import * as fs20 from "node:fs/promises";
|
|
19690
|
+
import * as path27 from "node:path";
|
|
19306
19691
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
19307
19692
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
19308
19693
|
const disposers = [];
|
|
@@ -19338,9 +19723,9 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
19338
19723
|
if (wpaths?.projectStatus) {
|
|
19339
19724
|
try {
|
|
19340
19725
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
19341
|
-
const dir =
|
|
19342
|
-
await
|
|
19343
|
-
await
|
|
19726
|
+
const dir = path27.dirname(statusFile);
|
|
19727
|
+
await fs20.mkdir(dir, { recursive: true });
|
|
19728
|
+
await fs20.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
19344
19729
|
} catch (err) {
|
|
19345
19730
|
console.error(
|
|
19346
19731
|
JSON.stringify({
|
|
@@ -20365,7 +20750,7 @@ var SpecsWebSocketHandler = class {
|
|
|
20365
20750
|
// src/server/start-webui.ts
|
|
20366
20751
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
20367
20752
|
import * as http2 from "node:http";
|
|
20368
|
-
import * as
|
|
20753
|
+
import * as path34 from "node:path";
|
|
20369
20754
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
20370
20755
|
import { getSharedProjectMailbox as getSharedProjectMailbox5, resolveProjectDir as resolveProjectDir4 } from "@wrongstack/core/coordination";
|
|
20371
20756
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
@@ -20379,7 +20764,7 @@ import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } fro
|
|
|
20379
20764
|
import {
|
|
20380
20765
|
expectDefined as expectDefined4,
|
|
20381
20766
|
sessionScopedPath as sessionScopedPath3,
|
|
20382
|
-
|
|
20767
|
+
startSharedHeapWatchdog,
|
|
20383
20768
|
toErrorMessage as toErrorMessage14,
|
|
20384
20769
|
wstackGlobalRoot as wstackGlobalRoot4
|
|
20385
20770
|
} from "@wrongstack/core/utils";
|
|
@@ -20388,10 +20773,8 @@ import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
|
20388
20773
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
20389
20774
|
|
|
20390
20775
|
// src/server/backend-services.ts
|
|
20391
|
-
import { join as
|
|
20392
|
-
import {
|
|
20393
|
-
Agent
|
|
20394
|
-
} from "@wrongstack/core/agent";
|
|
20776
|
+
import { join as join16 } from "node:path";
|
|
20777
|
+
import { Agent } from "@wrongstack/core/agent";
|
|
20395
20778
|
import {
|
|
20396
20779
|
BrainDecisionLedger,
|
|
20397
20780
|
BrainMonitor,
|
|
@@ -20403,8 +20786,8 @@ import {
|
|
|
20403
20786
|
mailboxSessionTag,
|
|
20404
20787
|
ObservableBrainArbiter as ObservableBrainArbiterCtor
|
|
20405
20788
|
} from "@wrongstack/core/coordination";
|
|
20406
|
-
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
20407
20789
|
import { DEFAULT_TOOLS_CONFIG } from "@wrongstack/core/defaults";
|
|
20790
|
+
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
20408
20791
|
import {
|
|
20409
20792
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
20410
20793
|
createBrainRuntime,
|
|
@@ -20414,7 +20797,9 @@ import {
|
|
|
20414
20797
|
} from "@wrongstack/core/execution";
|
|
20415
20798
|
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
20416
20799
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
20417
|
-
import {
|
|
20800
|
+
import {
|
|
20801
|
+
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
20802
|
+
} from "@wrongstack/core/types";
|
|
20418
20803
|
import {
|
|
20419
20804
|
estimateRequestTokensCalibrated,
|
|
20420
20805
|
toErrorMessage as toErrorMessage10
|
|
@@ -20431,7 +20816,7 @@ import {
|
|
|
20431
20816
|
import { spawn as spawn4 } from "node:child_process";
|
|
20432
20817
|
import { createRequire } from "node:module";
|
|
20433
20818
|
import { existsSync as existsSync2 } from "node:fs";
|
|
20434
|
-
import { dirname as dirname10, join as
|
|
20819
|
+
import { dirname as dirname10, join as join14 } from "node:path";
|
|
20435
20820
|
import { resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
|
|
20436
20821
|
import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
|
|
20437
20822
|
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
@@ -20560,7 +20945,7 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
20560
20945
|
function findWorkspaceCliEntry(projectRoot) {
|
|
20561
20946
|
let dir = projectRoot;
|
|
20562
20947
|
for (let i = 0; i < 6; i++) {
|
|
20563
|
-
const candidate =
|
|
20948
|
+
const candidate = join14(dir, "packages", "cli", "dist", "index.js");
|
|
20564
20949
|
if (existsSync2(candidate)) return candidate;
|
|
20565
20950
|
const parent = dirname10(dir);
|
|
20566
20951
|
if (parent === dir) return null;
|
|
@@ -20805,10 +21190,10 @@ function clampDim(value, fallback) {
|
|
|
20805
21190
|
}
|
|
20806
21191
|
|
|
20807
21192
|
// src/server/worktree-ws-handler.ts
|
|
20808
|
-
import { join as
|
|
21193
|
+
import { join as join15, resolve as resolve13, sep as sep5 } from "node:path";
|
|
21194
|
+
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
20809
21195
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
20810
21196
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
20811
|
-
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
20812
21197
|
var MAX_ACTIVITY = 6;
|
|
20813
21198
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
|
|
20814
21199
|
var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
|
|
@@ -20827,6 +21212,20 @@ var WorktreeWebSocketHandler = class {
|
|
|
20827
21212
|
baseBranch = "";
|
|
20828
21213
|
broadcastInterval = null;
|
|
20829
21214
|
offs = [];
|
|
21215
|
+
/**
|
|
21216
|
+
* Single-flight guard for orphan scans. Two concurrent `scanAndBroadcast()`
|
|
21217
|
+
* calls would race: the first call reads `wt.listManaged()`, then the
|
|
21218
|
+
* second call's mutation completes (e.g. `removeOne`), then the first call
|
|
21219
|
+
* broadcasts a stale orphan list — the user sees a row persist and clicks
|
|
21220
|
+
* Remove a second time, hitting "remove failed (not a managed worktree?)".
|
|
21221
|
+
*
|
|
21222
|
+
* We coalesce concurrent triggers: while a scan is in flight, additional
|
|
21223
|
+
* callers are remembered as "need a re-scan" and exactly one follow-up
|
|
21224
|
+
* scan runs after the in-flight one finishes. The re-scan picks up any
|
|
21225
|
+
* mutations that landed during the previous scan's `listManaged()` window.
|
|
21226
|
+
*/
|
|
21227
|
+
scanInFlight = null;
|
|
21228
|
+
scanRescanNeeded = false;
|
|
20830
21229
|
addClient(ws) {
|
|
20831
21230
|
this.clients.add(ws);
|
|
20832
21231
|
ws.on("close", () => this.clients.delete(ws));
|
|
@@ -20845,7 +21244,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
20845
21244
|
return true;
|
|
20846
21245
|
}
|
|
20847
21246
|
if (msg.type === "worktree.remove") {
|
|
20848
|
-
await this.removeOne(
|
|
21247
|
+
await this.removeOne(
|
|
21248
|
+
msg.payload?.["dir"],
|
|
21249
|
+
msg.payload?.["branch"]
|
|
21250
|
+
);
|
|
20849
21251
|
return true;
|
|
20850
21252
|
}
|
|
20851
21253
|
if (msg.type === "worktree.merge") {
|
|
@@ -20853,7 +21255,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
20853
21255
|
return true;
|
|
20854
21256
|
}
|
|
20855
21257
|
if (msg.type === "worktree.diff") {
|
|
20856
|
-
await this.diffOne(
|
|
21258
|
+
await this.diffOne(
|
|
21259
|
+
msg.payload?.["dir"],
|
|
21260
|
+
msg.payload?.["baseBranch"]
|
|
21261
|
+
);
|
|
20857
21262
|
return true;
|
|
20858
21263
|
}
|
|
20859
21264
|
return false;
|
|
@@ -20866,7 +21271,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
20866
21271
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
20867
21272
|
/** Absolute managed-worktrees root for this project. */
|
|
20868
21273
|
worktreesRoot() {
|
|
20869
|
-
return resolve13(
|
|
21274
|
+
return resolve13(join15(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
20870
21275
|
}
|
|
20871
21276
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
20872
21277
|
underRoot(dir) {
|
|
@@ -20882,12 +21287,43 @@ var WorktreeWebSocketHandler = class {
|
|
|
20882
21287
|
}
|
|
20883
21288
|
return live;
|
|
20884
21289
|
}
|
|
21290
|
+
/**
|
|
21291
|
+
* Coalesced orphan scan entry point. Multiple concurrent callers share one
|
|
21292
|
+
* in-flight scan; if a caller arrives during a scan, a single follow-up
|
|
21293
|
+
* scan is scheduled to pick up mutations that landed mid-scan. Returns the
|
|
21294
|
+
* shared promise so `await this.scanAndBroadcast()` still works for callers
|
|
21295
|
+
* that want to chain after the next broadcast.
|
|
21296
|
+
*/
|
|
21297
|
+
scanAndBroadcast() {
|
|
21298
|
+
if (this.scanInFlight) {
|
|
21299
|
+
this.scanRescanNeeded = true;
|
|
21300
|
+
return this.scanInFlight;
|
|
21301
|
+
}
|
|
21302
|
+
this.scanInFlight = this.runScanAndMaybeRescan();
|
|
21303
|
+
return this.scanInFlight;
|
|
21304
|
+
}
|
|
21305
|
+
/**
|
|
21306
|
+
* Run the actual scan, then drain a pending re-scan request if one came in
|
|
21307
|
+
* during the scan. The re-scan is bounded (one follow-up) — additional
|
|
21308
|
+
* requests that arrive during the follow-up coalesce into the next one.
|
|
21309
|
+
*/
|
|
21310
|
+
async runScanAndMaybeRescan() {
|
|
21311
|
+
try {
|
|
21312
|
+
await this.runScanOnce();
|
|
21313
|
+
while (this.scanRescanNeeded) {
|
|
21314
|
+
this.scanRescanNeeded = false;
|
|
21315
|
+
await this.runScanOnce();
|
|
21316
|
+
}
|
|
21317
|
+
} finally {
|
|
21318
|
+
this.scanInFlight = null;
|
|
21319
|
+
}
|
|
21320
|
+
}
|
|
20885
21321
|
/**
|
|
20886
21322
|
* Scan the disk for managed worktrees/branches NOT owned by a live in-session
|
|
20887
21323
|
* run and broadcast them as orphans, with whether it is safe to clean now.
|
|
20888
21324
|
* No-op (empty inventory) when management deps were not wired.
|
|
20889
21325
|
*/
|
|
20890
|
-
async
|
|
21326
|
+
async runScanOnce() {
|
|
20891
21327
|
if (!this.management) {
|
|
20892
21328
|
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
20893
21329
|
return;
|
|
@@ -20943,7 +21379,8 @@ var WorktreeWebSocketHandler = class {
|
|
|
20943
21379
|
}
|
|
20944
21380
|
const res = await cleanupStaleSddWorktrees2({
|
|
20945
21381
|
projectRoot: this.management.projectRoot,
|
|
20946
|
-
boardsDir: this.management.boardsDir
|
|
21382
|
+
boardsDir: this.management.boardsDir,
|
|
21383
|
+
stateTransport: "kanban"
|
|
20947
21384
|
});
|
|
20948
21385
|
if (res.skippedReason) {
|
|
20949
21386
|
this.broadcast({
|
|
@@ -20956,26 +21393,45 @@ var WorktreeWebSocketHandler = class {
|
|
|
20956
21393
|
for (const [id, h] of [...this.handles]) {
|
|
20957
21394
|
if (!ACTIVE_STATUSES.has(h.status)) this.handles.delete(id);
|
|
20958
21395
|
}
|
|
20959
|
-
this.broadcast({
|
|
21396
|
+
this.broadcast({
|
|
21397
|
+
type: "worktree.cleanup_result",
|
|
21398
|
+
payload: { ok: true, removed: res.removed }
|
|
21399
|
+
});
|
|
20960
21400
|
this.broadcastState();
|
|
20961
21401
|
await this.scanAndBroadcast();
|
|
20962
21402
|
}
|
|
20963
21403
|
/** Remove/discard ONE worktree + branch. Refused while a live run owns it. */
|
|
20964
21404
|
async removeOne(dir, branch) {
|
|
20965
21405
|
if (!this.management || !dir && !branch) {
|
|
20966
|
-
this.broadcast({
|
|
21406
|
+
this.broadcast({
|
|
21407
|
+
type: "worktree.cleanup_result",
|
|
21408
|
+
payload: { ok: false, removed: 0, reason: "nothing to remove" }
|
|
21409
|
+
});
|
|
20967
21410
|
return;
|
|
20968
21411
|
}
|
|
20969
21412
|
if (branch && !MANAGED_BRANCH_RE.test(branch)) {
|
|
20970
|
-
this.broadcast({
|
|
21413
|
+
this.broadcast({
|
|
21414
|
+
type: "worktree.cleanup_result",
|
|
21415
|
+
payload: { ok: false, removed: 0, reason: "not a managed worktree branch" }
|
|
21416
|
+
});
|
|
20971
21417
|
return;
|
|
20972
21418
|
}
|
|
20973
21419
|
if (dir && !this.underRoot(dir)) {
|
|
20974
|
-
this.broadcast({
|
|
21420
|
+
this.broadcast({
|
|
21421
|
+
type: "worktree.cleanup_result",
|
|
21422
|
+
payload: { ok: false, removed: 0, reason: "path is outside the managed worktrees root" }
|
|
21423
|
+
});
|
|
20975
21424
|
return;
|
|
20976
21425
|
}
|
|
20977
21426
|
if (branch && this.liveActiveBranches().has(branch)) {
|
|
20978
|
-
this.broadcast({
|
|
21427
|
+
this.broadcast({
|
|
21428
|
+
type: "worktree.cleanup_result",
|
|
21429
|
+
payload: {
|
|
21430
|
+
ok: false,
|
|
21431
|
+
removed: 0,
|
|
21432
|
+
reason: "a run is live on this worktree \u2014 stop it first"
|
|
21433
|
+
}
|
|
21434
|
+
});
|
|
20979
21435
|
return;
|
|
20980
21436
|
}
|
|
20981
21437
|
let removed = false;
|
|
@@ -20984,31 +21440,54 @@ var WorktreeWebSocketHandler = class {
|
|
|
20984
21440
|
({ removed } = await wt.removeOne(dir, branch));
|
|
20985
21441
|
}
|
|
20986
21442
|
for (const [id, h] of [...this.handles]) {
|
|
20987
|
-
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId))
|
|
21443
|
+
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId))
|
|
21444
|
+
this.handles.delete(id);
|
|
20988
21445
|
}
|
|
20989
|
-
this.broadcast({
|
|
21446
|
+
this.broadcast({
|
|
21447
|
+
type: "worktree.cleanup_result",
|
|
21448
|
+
payload: {
|
|
21449
|
+
ok: removed,
|
|
21450
|
+
removed: removed ? 1 : 0,
|
|
21451
|
+
reason: removed ? void 0 : "remove failed (not a managed worktree?)"
|
|
21452
|
+
}
|
|
21453
|
+
});
|
|
20990
21454
|
this.broadcastState();
|
|
20991
21455
|
await this.scanAndBroadcast();
|
|
20992
21456
|
}
|
|
20993
21457
|
/** Squash-merge ONE branch into base. Refused while a live run owns it. */
|
|
20994
21458
|
async mergeBranch(branch) {
|
|
20995
21459
|
if (!this.management || !branch) {
|
|
20996
|
-
this.broadcast({
|
|
21460
|
+
this.broadcast({
|
|
21461
|
+
type: "worktree.merge_result",
|
|
21462
|
+
payload: { ok: false, branch: branch ?? "", reason: "no branch" }
|
|
21463
|
+
});
|
|
20997
21464
|
return;
|
|
20998
21465
|
}
|
|
20999
21466
|
if (!MANAGED_BRANCH_RE.test(branch)) {
|
|
21000
|
-
this.broadcast({
|
|
21467
|
+
this.broadcast({
|
|
21468
|
+
type: "worktree.merge_result",
|
|
21469
|
+
payload: { ok: false, branch, reason: "not a managed worktree branch" }
|
|
21470
|
+
});
|
|
21001
21471
|
return;
|
|
21002
21472
|
}
|
|
21003
21473
|
if (this.liveActiveBranches().has(branch)) {
|
|
21004
|
-
this.broadcast({
|
|
21474
|
+
this.broadcast({
|
|
21475
|
+
type: "worktree.merge_result",
|
|
21476
|
+
payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" }
|
|
21477
|
+
});
|
|
21005
21478
|
return;
|
|
21006
21479
|
}
|
|
21007
21480
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
21008
21481
|
const res = await wt.mergeBranch(branch);
|
|
21009
21482
|
this.broadcast({
|
|
21010
21483
|
type: "worktree.merge_result",
|
|
21011
|
-
payload: {
|
|
21484
|
+
payload: {
|
|
21485
|
+
ok: res.ok,
|
|
21486
|
+
branch,
|
|
21487
|
+
conflict: res.conflict,
|
|
21488
|
+
conflictFiles: res.conflictFiles,
|
|
21489
|
+
reason: res.reason
|
|
21490
|
+
}
|
|
21012
21491
|
});
|
|
21013
21492
|
await this.scanAndBroadcast();
|
|
21014
21493
|
}
|
|
@@ -21050,8 +21529,14 @@ var WorktreeWebSocketHandler = class {
|
|
|
21050
21529
|
}),
|
|
21051
21530
|
on("worktree.committed", (p) => {
|
|
21052
21531
|
const e = p;
|
|
21053
|
-
this.patch(e.handleId, {
|
|
21054
|
-
|
|
21532
|
+
this.patch(e.handleId, {
|
|
21533
|
+
status: "committing",
|
|
21534
|
+
insertions: e.insertions,
|
|
21535
|
+
deletions: e.deletions,
|
|
21536
|
+
files: e.files
|
|
21537
|
+
});
|
|
21538
|
+
if (e.committed)
|
|
21539
|
+
this.activity(e.handleId, "committed", `+${e.insertions}/-${e.deletions} (${e.files}f)`);
|
|
21055
21540
|
this.broadcastState();
|
|
21056
21541
|
}),
|
|
21057
21542
|
on("worktree.merged", (p) => {
|
|
@@ -21092,10 +21577,15 @@ var WorktreeWebSocketHandler = class {
|
|
|
21092
21577
|
activity(id, kind, text2) {
|
|
21093
21578
|
const cur = this.handles.get(id);
|
|
21094
21579
|
if (cur) {
|
|
21095
|
-
const recentActivity = [...cur.recentActivity, { kind, text: text2, at: Date.now() }].slice(
|
|
21580
|
+
const recentActivity = [...cur.recentActivity, { kind, text: text2, at: Date.now() }].slice(
|
|
21581
|
+
-MAX_ACTIVITY
|
|
21582
|
+
);
|
|
21096
21583
|
this.handles.set(id, { ...cur, recentActivity });
|
|
21097
21584
|
}
|
|
21098
|
-
this.broadcast({
|
|
21585
|
+
this.broadcast({
|
|
21586
|
+
type: "worktree.event",
|
|
21587
|
+
payload: { kind, handleId: id, text: text2, at: Date.now() }
|
|
21588
|
+
});
|
|
21099
21589
|
}
|
|
21100
21590
|
stateMessage() {
|
|
21101
21591
|
return {
|
|
@@ -21337,7 +21827,7 @@ async function createAgentServices(input) {
|
|
|
21337
21827
|
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
21338
21828
|
fallbackModels: config.fallbackModels
|
|
21339
21829
|
});
|
|
21340
|
-
const brainLedgerPath =
|
|
21830
|
+
const brainLedgerPath = join16(wpaths.projectDir, "brain-ledger.jsonl");
|
|
21341
21831
|
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
21342
21832
|
let brainLedger;
|
|
21343
21833
|
const startBrainLedger = () => {
|
|
@@ -21402,33 +21892,35 @@ async function createAgentServices(input) {
|
|
|
21402
21892
|
brainLog.push(entry);
|
|
21403
21893
|
if (brainLog.length > 20) brainLog.shift();
|
|
21404
21894
|
};
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21413
|
-
|
|
21414
|
-
|
|
21415
|
-
|
|
21416
|
-
|
|
21417
|
-
|
|
21418
|
-
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
|
|
21895
|
+
const brainLogOffs = [
|
|
21896
|
+
events.on(
|
|
21897
|
+
"brain.decision_answered",
|
|
21898
|
+
(e) => pushBrainLog({
|
|
21899
|
+
at: e.at,
|
|
21900
|
+
kind: "answered",
|
|
21901
|
+
question: e.request.question,
|
|
21902
|
+
outcome: e.decision.type === "answer" ? e.decision.optionId ?? e.decision.text : ""
|
|
21903
|
+
})
|
|
21904
|
+
),
|
|
21905
|
+
events.on(
|
|
21906
|
+
"brain.decision_ask_human",
|
|
21907
|
+
(e) => pushBrainLog({
|
|
21908
|
+
at: e.at,
|
|
21909
|
+
kind: "ask_human",
|
|
21910
|
+
question: e.request.question,
|
|
21911
|
+
outcome: "needs human judgement"
|
|
21912
|
+
})
|
|
21913
|
+
),
|
|
21914
|
+
events.on(
|
|
21915
|
+
"brain.decision_denied",
|
|
21916
|
+
(e) => pushBrainLog({
|
|
21917
|
+
at: e.at,
|
|
21918
|
+
kind: "denied",
|
|
21919
|
+
question: e.request.question,
|
|
21920
|
+
outcome: e.decision.type === "deny" ? e.decision.reason : ""
|
|
21921
|
+
})
|
|
21922
|
+
)
|
|
21923
|
+
];
|
|
21432
21924
|
const brainMailbox = getSharedProjectMailbox3(wpaths.projectDir, events);
|
|
21433
21925
|
brainMonitor = new BrainMonitor({
|
|
21434
21926
|
events,
|
|
@@ -21540,6 +22032,17 @@ async function createAgentServices(input) {
|
|
|
21540
22032
|
getActiveSessionId: () => context.session.id
|
|
21541
22033
|
}
|
|
21542
22034
|
);
|
|
22035
|
+
let realtimeHandlersDisposed = false;
|
|
22036
|
+
const disposeRealtimeHandlers = () => {
|
|
22037
|
+
if (realtimeHandlersDisposed) return;
|
|
22038
|
+
realtimeHandlersDisposed = true;
|
|
22039
|
+
for (const off of brainLogOffs) off();
|
|
22040
|
+
goalHandler.dispose();
|
|
22041
|
+
sddBoardHandler.dispose();
|
|
22042
|
+
worktreeHandler.dispose();
|
|
22043
|
+
terminalHandler.dispose();
|
|
22044
|
+
collabHandler.dispose();
|
|
22045
|
+
};
|
|
21543
22046
|
return {
|
|
21544
22047
|
collabBus,
|
|
21545
22048
|
compactor,
|
|
@@ -21565,6 +22068,7 @@ async function createAgentServices(input) {
|
|
|
21565
22068
|
worktreeHandler,
|
|
21566
22069
|
terminalHandler,
|
|
21567
22070
|
collabHandler,
|
|
22071
|
+
disposeRealtimeHandlers,
|
|
21568
22072
|
updateAutoCompactionMaxContext
|
|
21569
22073
|
};
|
|
21570
22074
|
}
|
|
@@ -21651,7 +22155,7 @@ function createConnectionHandler(options) {
|
|
|
21651
22155
|
}
|
|
21652
22156
|
|
|
21653
22157
|
// src/server/message-dispatcher.ts
|
|
21654
|
-
import
|
|
22158
|
+
import path28 from "node:path";
|
|
21655
22159
|
function createMessageDispatcher(opts) {
|
|
21656
22160
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
21657
22161
|
function makeWorklistContext() {
|
|
@@ -21672,7 +22176,7 @@ function createMessageDispatcher(opts) {
|
|
|
21672
22176
|
skillLoader: deps2.skillLoader,
|
|
21673
22177
|
skillInstaller: deps2.skillInstaller,
|
|
21674
22178
|
projectRoot,
|
|
21675
|
-
projectSkillsDir:
|
|
22179
|
+
projectSkillsDir: path28.join(projectRoot, ".wrongstack", "skills"),
|
|
21676
22180
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
21677
22181
|
};
|
|
21678
22182
|
}
|
|
@@ -21924,7 +22428,7 @@ function createMessageDispatcher(opts) {
|
|
|
21924
22428
|
|
|
21925
22429
|
// src/server/pre-context-services.ts
|
|
21926
22430
|
import { createRequire as createRequire3 } from "node:module";
|
|
21927
|
-
import * as
|
|
22431
|
+
import * as path31 from "node:path";
|
|
21928
22432
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
21929
22433
|
import {
|
|
21930
22434
|
getSharedProjectMailbox as getSharedProjectMailbox4,
|
|
@@ -21978,8 +22482,8 @@ import { configureDangerBypass, configureExecPolicy } from "@wrongstack/tools";
|
|
|
21978
22482
|
import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
|
|
21979
22483
|
|
|
21980
22484
|
// src/server/model-auto-discovery.ts
|
|
21981
|
-
import * as
|
|
21982
|
-
import * as
|
|
22485
|
+
import * as fs21 from "node:fs/promises";
|
|
22486
|
+
import * as path29 from "node:path";
|
|
21983
22487
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
21984
22488
|
function isOverlayRegistry(value) {
|
|
21985
22489
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -22005,7 +22509,7 @@ function eligibleProviders(config) {
|
|
|
22005
22509
|
}
|
|
22006
22510
|
async function readCache(file) {
|
|
22007
22511
|
try {
|
|
22008
|
-
return JSON.parse(await
|
|
22512
|
+
return JSON.parse(await fs21.readFile(file, "utf8"));
|
|
22009
22513
|
} catch {
|
|
22010
22514
|
return {};
|
|
22011
22515
|
}
|
|
@@ -22015,7 +22519,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
22015
22519
|
if (!isOverlayRegistry(registry)) return;
|
|
22016
22520
|
const targets = eligibleProviders(opts.config);
|
|
22017
22521
|
if (targets.length === 0) return;
|
|
22018
|
-
const cacheFile =
|
|
22522
|
+
const cacheFile = path29.join(opts.cacheDir, "discovered-models-cache.json");
|
|
22019
22523
|
const cache2 = await readCache(cacheFile);
|
|
22020
22524
|
let cacheDirty = false;
|
|
22021
22525
|
await Promise.all(
|
|
@@ -22052,8 +22556,8 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
22052
22556
|
);
|
|
22053
22557
|
if (cacheDirty) {
|
|
22054
22558
|
try {
|
|
22055
|
-
await
|
|
22056
|
-
await
|
|
22559
|
+
await fs21.mkdir(path29.dirname(cacheFile), { recursive: true });
|
|
22560
|
+
await fs21.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
22057
22561
|
} catch {
|
|
22058
22562
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
22059
22563
|
}
|
|
@@ -22149,7 +22653,7 @@ function resolveSetupProvider(opts) {
|
|
|
22149
22653
|
}
|
|
22150
22654
|
|
|
22151
22655
|
// src/server/standalone-session-identity.ts
|
|
22152
|
-
import * as
|
|
22656
|
+
import * as path30 from "node:path";
|
|
22153
22657
|
import {
|
|
22154
22658
|
AgentStatusTracker,
|
|
22155
22659
|
FleetNotifier,
|
|
@@ -22168,7 +22672,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
22168
22672
|
let activeTarget = {
|
|
22169
22673
|
projectSlug: paths.projectSlug,
|
|
22170
22674
|
projectRoot: paths.projectRoot,
|
|
22171
|
-
projectName:
|
|
22675
|
+
projectName: path30.basename(paths.projectRoot),
|
|
22172
22676
|
workingDir: opts.workingDir
|
|
22173
22677
|
};
|
|
22174
22678
|
let pendingClaim;
|
|
@@ -22399,7 +22903,7 @@ async function createPreContextServices(input) {
|
|
|
22399
22903
|
await discoverAndMergeWebuiProviders({
|
|
22400
22904
|
config,
|
|
22401
22905
|
registry: modelsRegistry,
|
|
22402
|
-
cacheDir:
|
|
22906
|
+
cacheDir: path31.dirname(wpaths.modelsCache),
|
|
22403
22907
|
logger
|
|
22404
22908
|
});
|
|
22405
22909
|
} catch (err) {
|
|
@@ -22451,7 +22955,7 @@ async function createPreContextServices(input) {
|
|
|
22451
22955
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
22452
22956
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
22453
22957
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
22454
|
-
|
|
22958
|
+
path31.join(wpaths.projectDir, "mcp-auth.json"),
|
|
22455
22959
|
vault
|
|
22456
22960
|
);
|
|
22457
22961
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -22557,7 +23061,7 @@ async function createPreContextServices(input) {
|
|
|
22557
23061
|
};
|
|
22558
23062
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
22559
23063
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
22560
|
-
manifestPath:
|
|
23064
|
+
manifestPath: path31.join(wpaths.configDir, "installed-skills.json"),
|
|
22561
23065
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
22562
23066
|
globalSkillsDir: wpaths.globalSkills,
|
|
22563
23067
|
projectHash: wpaths.projectHash,
|
|
@@ -22567,8 +23071,8 @@ async function createPreContextServices(input) {
|
|
|
22567
23071
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
22568
23072
|
try {
|
|
22569
23073
|
const req = createRequire3(import.meta.url);
|
|
22570
|
-
return
|
|
22571
|
-
|
|
23074
|
+
return path31.join(
|
|
23075
|
+
path31.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
22572
23076
|
"data",
|
|
22573
23077
|
"prompts"
|
|
22574
23078
|
);
|
|
@@ -22672,7 +23176,7 @@ async function createPreContextServices(input) {
|
|
|
22672
23176
|
}
|
|
22673
23177
|
|
|
22674
23178
|
// src/server/routes.ts
|
|
22675
|
-
import
|
|
23179
|
+
import path32 from "node:path";
|
|
22676
23180
|
import { makeProviderFromConfig as makeProviderFromConfig4, withCatalogCapabilities } from "@wrongstack/providers";
|
|
22677
23181
|
|
|
22678
23182
|
// src/server/mode-handlers.ts
|
|
@@ -22964,7 +23468,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22964
23468
|
};
|
|
22965
23469
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
22966
23470
|
getProjectRoot: state.getProjectRoot,
|
|
22967
|
-
getGlobalRoot: () =>
|
|
23471
|
+
getGlobalRoot: () => path32.dirname(deps2.globalConfigPath),
|
|
22968
23472
|
events: deps2.events
|
|
22969
23473
|
});
|
|
22970
23474
|
const mcpRoutes = {
|
|
@@ -23037,7 +23541,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
23037
23541
|
}
|
|
23038
23542
|
|
|
23039
23543
|
// src/server/server-runtime.ts
|
|
23040
|
-
import * as
|
|
23544
|
+
import * as path33 from "node:path";
|
|
23041
23545
|
import { createRequire as createRequire4 } from "node:module";
|
|
23042
23546
|
import { fileURLToPath } from "node:url";
|
|
23043
23547
|
import { WebSocketServer } from "ws";
|
|
@@ -23098,7 +23602,7 @@ function createSessionStartPayload(g) {
|
|
|
23098
23602
|
inputCost,
|
|
23099
23603
|
outputCost,
|
|
23100
23604
|
cacheReadCost,
|
|
23101
|
-
projectName:
|
|
23605
|
+
projectName: path33.basename(projectRoot) || projectRoot,
|
|
23102
23606
|
projectRoot,
|
|
23103
23607
|
cwd: g.getWorkingDir(),
|
|
23104
23608
|
mode: g.getModeId(),
|
|
@@ -23186,13 +23690,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
23186
23690
|
};
|
|
23187
23691
|
}
|
|
23188
23692
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
23189
|
-
if (explicitDistDir) return
|
|
23693
|
+
if (explicitDistDir) return path33.resolve(explicitDistDir);
|
|
23190
23694
|
try {
|
|
23191
23695
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
23192
23696
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
23193
|
-
return
|
|
23697
|
+
return path33.dirname(serverEntry);
|
|
23194
23698
|
} catch {
|
|
23195
|
-
return
|
|
23699
|
+
return path33.resolve(path33.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
23196
23700
|
}
|
|
23197
23701
|
}
|
|
23198
23702
|
function startHttpServer(opts) {
|
|
@@ -23406,6 +23910,7 @@ async function startWebUI(opts = {}) {
|
|
|
23406
23910
|
worktreeHandler,
|
|
23407
23911
|
terminalHandler,
|
|
23408
23912
|
collabHandler,
|
|
23913
|
+
disposeRealtimeHandlers,
|
|
23409
23914
|
updateAutoCompactionMaxContext
|
|
23410
23915
|
} = agentServices;
|
|
23411
23916
|
if (typeof context.meta["yolo"] === "boolean") {
|
|
@@ -23584,21 +24089,21 @@ async function startWebUI(opts = {}) {
|
|
|
23584
24089
|
});
|
|
23585
24090
|
}
|
|
23586
24091
|
async function touchProjectEntry(root, workDir) {
|
|
23587
|
-
const resolved =
|
|
24092
|
+
const resolved = path34.resolve(root);
|
|
23588
24093
|
const manifest = await loadManifest(globalConfigPath);
|
|
23589
24094
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23590
|
-
const existing = manifest.projects.find((p) =>
|
|
24095
|
+
const existing = manifest.projects.find((p) => path34.resolve(p.root) === resolved);
|
|
23591
24096
|
if (existing) {
|
|
23592
24097
|
existing.lastSeen = now;
|
|
23593
|
-
if (workDir) existing.lastWorkingDir =
|
|
24098
|
+
if (workDir) existing.lastWorkingDir = path34.resolve(workDir);
|
|
23594
24099
|
} else {
|
|
23595
24100
|
manifest.projects.push({
|
|
23596
|
-
name:
|
|
24101
|
+
name: path34.basename(resolved),
|
|
23597
24102
|
root: resolved,
|
|
23598
24103
|
slug: generateProjectSlug(resolved),
|
|
23599
24104
|
createdAt: now,
|
|
23600
24105
|
lastSeen: now,
|
|
23601
|
-
lastWorkingDir: workDir ?
|
|
24106
|
+
lastWorkingDir: workDir ? path34.resolve(workDir) : void 0
|
|
23602
24107
|
});
|
|
23603
24108
|
}
|
|
23604
24109
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -23775,7 +24280,20 @@ async function startWebUI(opts = {}) {
|
|
|
23775
24280
|
);
|
|
23776
24281
|
credentialWatcherClose = credentialWatcher.close;
|
|
23777
24282
|
}
|
|
23778
|
-
const stopHeapWatchdog =
|
|
24283
|
+
const stopHeapWatchdog = startSharedHeapWatchdog({
|
|
24284
|
+
collectStats: () => ({
|
|
24285
|
+
surface: opts.surface ?? "webui",
|
|
24286
|
+
sessionId: context.session.id,
|
|
24287
|
+
messages: context.state.messages.length,
|
|
24288
|
+
messageEstimatedTokens: context.state.messages.reduce(
|
|
24289
|
+
(sum, message) => sum + (message._estTokens ?? 0),
|
|
24290
|
+
0
|
|
24291
|
+
),
|
|
24292
|
+
webClients: clients.size,
|
|
24293
|
+
pendingConfirms: pendingConfirms.size,
|
|
24294
|
+
runActive: runLockControl.get() !== null
|
|
24295
|
+
})
|
|
24296
|
+
});
|
|
23779
24297
|
const routes = buildRoutes(state, deps2, cb);
|
|
23780
24298
|
const handleMessage = createMessageDispatcher({
|
|
23781
24299
|
state,
|
|
@@ -23853,6 +24371,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23853
24371
|
await todosCheckpoint.detach();
|
|
23854
24372
|
await stopHeapWatchdog();
|
|
23855
24373
|
credentialWatcherClose?.();
|
|
24374
|
+
disposeRealtimeHandlers();
|
|
23856
24375
|
brainMonitor.stop();
|
|
23857
24376
|
await agentServices.brainLedger?.stop().catch(() => {
|
|
23858
24377
|
});
|
|
@@ -23876,7 +24395,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23876
24395
|
await memoryStore.dispose().catch(
|
|
23877
24396
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage14(err)}`)
|
|
23878
24397
|
);
|
|
23879
|
-
await unregisterInstance(process.pid,
|
|
24398
|
+
await unregisterInstance(process.pid, path34.dirname(globalConfigPath));
|
|
23880
24399
|
}
|
|
23881
24400
|
});
|
|
23882
24401
|
}
|