@wrongstack/webui-server 0.296.4 → 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 +934 -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/embedded-lifecycle.d.ts +2 -0
- package/dist/server/embedded-lifecycle.d.ts.map +1 -1
- package/dist/server/entry.js +856 -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
|
}
|
|
@@ -5746,15 +5733,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5746
5733
|
if (!m) continue;
|
|
5747
5734
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5748
5735
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5749
|
-
let
|
|
5750
|
-
if (
|
|
5736
|
+
let path35 = m[3] ?? "";
|
|
5737
|
+
if (path35 === "") {
|
|
5751
5738
|
i += 1;
|
|
5752
|
-
|
|
5739
|
+
path35 = parts[i + 1] ?? parts[i] ?? "";
|
|
5753
5740
|
i += 1;
|
|
5754
5741
|
}
|
|
5755
|
-
if (!
|
|
5756
|
-
const prev = counts.get(
|
|
5757
|
-
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 });
|
|
5758
5745
|
}
|
|
5759
5746
|
};
|
|
5760
5747
|
parseNumstat(unstagedNumstat);
|
|
@@ -5766,7 +5753,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5766
5753
|
if (!rec || rec.length < 3) continue;
|
|
5767
5754
|
const x = rec[0] ?? " ";
|
|
5768
5755
|
const y = rec[1] ?? " ";
|
|
5769
|
-
const
|
|
5756
|
+
const path35 = rec.slice(3);
|
|
5770
5757
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5771
5758
|
if (isRename) i += 1;
|
|
5772
5759
|
let status;
|
|
@@ -5778,13 +5765,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5778
5765
|
else if (x === "D" || y === "D") status = "D";
|
|
5779
5766
|
else status = "M";
|
|
5780
5767
|
const staged = x !== " " && x !== "?";
|
|
5781
|
-
let added = counts.get(
|
|
5782
|
-
let deleted = counts.get(
|
|
5768
|
+
let added = counts.get(path35)?.added ?? 0;
|
|
5769
|
+
let deleted = counts.get(path35)?.deleted ?? 0;
|
|
5783
5770
|
if (status === "?") {
|
|
5784
5771
|
added = 0;
|
|
5785
5772
|
deleted = 0;
|
|
5786
5773
|
}
|
|
5787
|
-
files.push({ path:
|
|
5774
|
+
files.push({ path: path35, status, added, deleted, staged });
|
|
5788
5775
|
}
|
|
5789
5776
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5790
5777
|
} catch (err) {
|
|
@@ -5795,21 +5782,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5795
5782
|
}
|
|
5796
5783
|
}
|
|
5797
5784
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5798
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5785
|
+
async function handleGitDiff(ws, projectRoot, path35) {
|
|
5799
5786
|
const cwd = projectRoot || void 0;
|
|
5800
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5801
|
-
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)) {
|
|
5802
5789
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5803
5790
|
return;
|
|
5804
5791
|
}
|
|
5805
5792
|
try {
|
|
5806
5793
|
const git = makeGit(cwd);
|
|
5807
5794
|
const { readFile: readFile13 } = await import("node:fs/promises");
|
|
5808
|
-
const { join:
|
|
5809
|
-
const oldText = await git(["show", `HEAD:${
|
|
5795
|
+
const { join: join19 } = await import("node:path");
|
|
5796
|
+
const oldText = await git(["show", `HEAD:${path35}`]);
|
|
5810
5797
|
let newText = "";
|
|
5811
5798
|
try {
|
|
5812
|
-
const abs = cwd ?
|
|
5799
|
+
const abs = cwd ? join19(cwd, path35) : path35;
|
|
5813
5800
|
const buf = await readFile13(abs);
|
|
5814
5801
|
if (buf.includes(0)) {
|
|
5815
5802
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5871,10 +5858,7 @@ async function handleGoalSnapshotRoute(ws, msg, handlers) {
|
|
|
5871
5858
|
}
|
|
5872
5859
|
|
|
5873
5860
|
// src/server/goal-ws-handler.ts
|
|
5874
|
-
import {
|
|
5875
|
-
import {
|
|
5876
|
-
assignNickname
|
|
5877
|
-
} from "@wrongstack/core/coordination";
|
|
5861
|
+
import { assignNickname } from "@wrongstack/core/coordination";
|
|
5878
5862
|
import {
|
|
5879
5863
|
GoalAssessor,
|
|
5880
5864
|
GoalPlanner,
|
|
@@ -5882,6 +5866,7 @@ import {
|
|
|
5882
5866
|
PhaseOrchestrator,
|
|
5883
5867
|
PhaseStore
|
|
5884
5868
|
} from "@wrongstack/core/goal";
|
|
5869
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
5885
5870
|
import { WorktreeManager } from "@wrongstack/core/worktree";
|
|
5886
5871
|
|
|
5887
5872
|
// src/server/git-process.ts
|
|
@@ -5918,12 +5903,7 @@ function deriveTitle(goal) {
|
|
|
5918
5903
|
return trimmed || "Goal";
|
|
5919
5904
|
}
|
|
5920
5905
|
async function commitsSince(cwd, baseSha, branch) {
|
|
5921
|
-
const output = await gitStdout(cwd, [
|
|
5922
|
-
"log",
|
|
5923
|
-
"--reverse",
|
|
5924
|
-
"--format=%H",
|
|
5925
|
-
`${baseSha}..${branch}`
|
|
5926
|
-
]);
|
|
5906
|
+
const output = await gitStdout(cwd, ["log", "--reverse", "--format=%H", `${baseSha}..${branch}`]);
|
|
5927
5907
|
if (output === null) return [];
|
|
5928
5908
|
return output.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
5929
5909
|
}
|
|
@@ -5974,6 +5954,17 @@ var GoalWebSocketHandler = class {
|
|
|
5974
5954
|
ws.on("error", () => this.clients.delete(client));
|
|
5975
5955
|
this.sendState(client);
|
|
5976
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
|
+
}
|
|
5977
5968
|
async handleMessage(ws, msg) {
|
|
5978
5969
|
switch (msg.type) {
|
|
5979
5970
|
case "goal.assess":
|
|
@@ -6021,7 +6012,8 @@ var GoalWebSocketHandler = class {
|
|
|
6021
6012
|
}
|
|
6022
6013
|
case "goal.assignTask": {
|
|
6023
6014
|
const { taskId, agentId, agentName } = msg.payload;
|
|
6024
|
-
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
6015
|
+
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
6016
|
+
this.afterBoardMutation();
|
|
6025
6017
|
break;
|
|
6026
6018
|
}
|
|
6027
6019
|
case "goal.addTask": {
|
|
@@ -6066,7 +6058,10 @@ var GoalWebSocketHandler = class {
|
|
|
6066
6058
|
this.graph = graph;
|
|
6067
6059
|
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
6068
6060
|
} else {
|
|
6069
|
-
this.broadcast({
|
|
6061
|
+
this.broadcast({
|
|
6062
|
+
type: "goal.error",
|
|
6063
|
+
payload: { message: `Graph not found: ${graphId}` }
|
|
6064
|
+
});
|
|
6070
6065
|
}
|
|
6071
6066
|
}
|
|
6072
6067
|
break;
|
|
@@ -6089,10 +6084,13 @@ var GoalWebSocketHandler = class {
|
|
|
6089
6084
|
const mySeq = ++this.assessSeq;
|
|
6090
6085
|
const sendResult7 = (result) => {
|
|
6091
6086
|
if (mySeq !== this.assessSeq) return;
|
|
6092
|
-
sendSerialized(
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6087
|
+
sendSerialized(
|
|
6088
|
+
ws,
|
|
6089
|
+
JSON.stringify({
|
|
6090
|
+
type: "goal.assess.result",
|
|
6091
|
+
payload: { ...result, reqSeq: seq }
|
|
6092
|
+
})
|
|
6093
|
+
);
|
|
6096
6094
|
};
|
|
6097
6095
|
if (!goal.trim()) {
|
|
6098
6096
|
sendResult7({
|
|
@@ -6138,15 +6136,24 @@ var GoalWebSocketHandler = class {
|
|
|
6138
6136
|
const multiBoard = payload?.multiBoard ?? false;
|
|
6139
6137
|
const verifyTasks = payload?.verifyTasks ?? false;
|
|
6140
6138
|
const chimeraReview = payload?.chimeraReview ?? false;
|
|
6141
|
-
|
|
6139
|
+
const runAbort = new AbortController();
|
|
6140
|
+
this.abort = runAbort;
|
|
6142
6141
|
this.stopping = false;
|
|
6143
|
-
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal,
|
|
6144
|
-
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) {
|
|
6145
6144
|
this.broadcast({ type: "goal.stopped", payload: { title } });
|
|
6146
6145
|
return;
|
|
6147
6146
|
}
|
|
6148
6147
|
this.logger.info(`[Goal] Starting: ${title}`);
|
|
6149
|
-
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();
|
|
6150
6157
|
this.graph = graph;
|
|
6151
6158
|
await this.store.save(graph);
|
|
6152
6159
|
const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
|
|
@@ -6165,9 +6172,10 @@ var GoalWebSocketHandler = class {
|
|
|
6165
6172
|
maybeVerify.verifyPhase = (async (phase, env) => {
|
|
6166
6173
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
6167
6174
|
try {
|
|
6168
|
-
const {
|
|
6175
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
6169
6176
|
const result = await new Promise((resolve16) => {
|
|
6170
|
-
|
|
6177
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
6178
|
+
execFile2(npxCommand, ["tsc", "--noEmit"], { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
6171
6179
|
if (err && err.code === "ENOENT") {
|
|
6172
6180
|
resolve16("[verify] tsc not found \u2014 skipping");
|
|
6173
6181
|
return;
|
|
@@ -6309,11 +6317,41 @@ Run npx tsc --noEmit to verify the fix. Output the fixed file paths.`;
|
|
|
6309
6317
|
/** Generic fallback phases when the LLM planner produces nothing usable. */
|
|
6310
6318
|
defaultPhases() {
|
|
6311
6319
|
return [
|
|
6312
|
-
{
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
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
|
+
}
|
|
6317
6355
|
];
|
|
6318
6356
|
}
|
|
6319
6357
|
/** Plan phases+todos for the goal via the LLM; fall back to defaults on failure.
|
|
@@ -6394,8 +6432,10 @@ Type: ${task.type}`;
|
|
|
6394
6432
|
try {
|
|
6395
6433
|
const result_ = await this.agent.run(reviewPrompt);
|
|
6396
6434
|
if (result_.status === "done" && result_.finalText) {
|
|
6397
|
-
this.logger.info(
|
|
6398
|
-
${
|
|
6435
|
+
this.logger.info(
|
|
6436
|
+
`[Goal] Chimera review for "${task.title}":
|
|
6437
|
+
${result_.finalText.slice(0, 2e3)}`
|
|
6438
|
+
);
|
|
6399
6439
|
}
|
|
6400
6440
|
} catch (err) {
|
|
6401
6441
|
this.logger.warn(`[Goal] Chimera review failed for "${task.title}": ${toErrorMessage2(err)}`);
|
|
@@ -6448,7 +6488,16 @@ ${result_.finalText.slice(0, 2e3)}`);
|
|
|
6448
6488
|
}
|
|
6449
6489
|
buildState(activePhaseId) {
|
|
6450
6490
|
if (!this.graph) {
|
|
6451
|
-
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
|
+
};
|
|
6452
6501
|
}
|
|
6453
6502
|
const phases = Array.from(this.graph.phases.values());
|
|
6454
6503
|
const currentActiveId = activePhaseId || phases.find((p) => p.status === "running")?.id || phases[0]?.id || "";
|
|
@@ -6954,9 +7003,9 @@ function getAnalyticsBuffer() {
|
|
|
6954
7003
|
}
|
|
6955
7004
|
|
|
6956
7005
|
// src/server/http-server.ts
|
|
6957
|
-
import * as
|
|
7006
|
+
import * as fs10 from "node:fs/promises";
|
|
6958
7007
|
import * as http from "node:http";
|
|
6959
|
-
import * as
|
|
7008
|
+
import * as path13 from "node:path";
|
|
6960
7009
|
import * as v8 from "node:v8";
|
|
6961
7010
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6962
7011
|
|
|
@@ -7640,18 +7689,153 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7640
7689
|
}
|
|
7641
7690
|
}
|
|
7642
7691
|
|
|
7643
|
-
// src/server/
|
|
7692
|
+
// src/server/memory-diagnostics.ts
|
|
7644
7693
|
import * as fs8 from "node:fs/promises";
|
|
7645
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";
|
|
7646
7830
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7647
7831
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7648
7832
|
function projectsJsonPath(globalConfigPath) {
|
|
7649
|
-
const base =
|
|
7650
|
-
return
|
|
7833
|
+
const base = path12.dirname(globalConfigPath);
|
|
7834
|
+
return path12.join(base, "projects.json");
|
|
7651
7835
|
}
|
|
7652
7836
|
async function loadManifest(globalConfigPath) {
|
|
7653
7837
|
try {
|
|
7654
|
-
const raw = await
|
|
7838
|
+
const raw = await fs9.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
7655
7839
|
const parsed = JSON.parse(raw);
|
|
7656
7840
|
return { projects: parsed.projects ?? [] };
|
|
7657
7841
|
} catch {
|
|
@@ -7660,37 +7844,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7660
7844
|
}
|
|
7661
7845
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7662
7846
|
const file = projectsJsonPath(globalConfigPath);
|
|
7663
|
-
await
|
|
7664
|
-
await
|
|
7847
|
+
await fs9.mkdir(path12.dirname(file), { recursive: true });
|
|
7848
|
+
await fs9.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7665
7849
|
}
|
|
7666
7850
|
function generateProjectSlug(rootPath) {
|
|
7667
7851
|
return projectSlug(rootPath);
|
|
7668
7852
|
}
|
|
7669
7853
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7670
|
-
const base =
|
|
7671
|
-
const dir =
|
|
7672
|
-
await
|
|
7854
|
+
const base = path12.dirname(globalConfigPath);
|
|
7855
|
+
const dir = path12.join(base, "projects", slug);
|
|
7856
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
7673
7857
|
return dir;
|
|
7674
7858
|
}
|
|
7675
7859
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7676
|
-
const root =
|
|
7860
|
+
const root = path12.resolve(options.projectRoot);
|
|
7677
7861
|
const file = projectsJsonPath(globalConfigPath);
|
|
7678
7862
|
let entry;
|
|
7679
7863
|
await withFileLock(file, async () => {
|
|
7680
7864
|
const manifest = await loadManifest(globalConfigPath);
|
|
7681
7865
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7682
|
-
entry = manifest.projects.find((candidate) =>
|
|
7866
|
+
entry = manifest.projects.find((candidate) => path12.resolve(candidate.root) === root);
|
|
7683
7867
|
if (entry) {
|
|
7684
7868
|
entry.lastSeen = now;
|
|
7685
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7869
|
+
if (options.workingDir) entry.lastWorkingDir = path12.resolve(options.workingDir);
|
|
7686
7870
|
} else {
|
|
7687
7871
|
entry = {
|
|
7688
|
-
name: options.name ??
|
|
7872
|
+
name: options.name ?? path12.basename(root),
|
|
7689
7873
|
root,
|
|
7690
7874
|
slug: generateProjectSlug(root),
|
|
7691
7875
|
createdAt: now,
|
|
7692
7876
|
lastSeen: now,
|
|
7693
|
-
lastWorkingDir: options.workingDir ?
|
|
7877
|
+
lastWorkingDir: options.workingDir ? path12.resolve(options.workingDir) : void 0
|
|
7694
7878
|
};
|
|
7695
7879
|
manifest.projects.push(entry);
|
|
7696
7880
|
}
|
|
@@ -8101,9 +8285,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
8101
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'`;
|
|
8102
8286
|
}
|
|
8103
8287
|
function isInsideDist(candidate, distDir) {
|
|
8104
|
-
const root =
|
|
8105
|
-
const resolved =
|
|
8106
|
-
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);
|
|
8107
8291
|
}
|
|
8108
8292
|
function decodeSessionId(segment) {
|
|
8109
8293
|
try {
|
|
@@ -8123,7 +8307,7 @@ function strictDecodeParam(segment, res) {
|
|
|
8123
8307
|
}
|
|
8124
8308
|
function createHttpServer(opts) {
|
|
8125
8309
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
8126
|
-
const distDir =
|
|
8310
|
+
const distDir = path13.resolve(opts.distDir);
|
|
8127
8311
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
8128
8312
|
let techStackRuntime = null;
|
|
8129
8313
|
const getTechStackRuntime = async () => {
|
|
@@ -8451,11 +8635,7 @@ function createHttpServer(opts) {
|
|
|
8451
8635
|
if (researchMatch && req.method === "POST") {
|
|
8452
8636
|
const pkg = strictDecodeParam(researchMatch[1], res);
|
|
8453
8637
|
if (pkg === null) return;
|
|
8454
|
-
await handleTechStackDependencyResearch(
|
|
8455
|
-
res,
|
|
8456
|
-
deps2,
|
|
8457
|
-
pkg
|
|
8458
|
-
);
|
|
8638
|
+
await handleTechStackDependencyResearch(res, deps2, pkg);
|
|
8459
8639
|
return;
|
|
8460
8640
|
}
|
|
8461
8641
|
} catch (error2) {
|
|
@@ -8491,6 +8671,7 @@ function createHttpServer(opts) {
|
|
|
8491
8671
|
return;
|
|
8492
8672
|
}
|
|
8493
8673
|
if (url.pathname === "/debug/system" && req.method === "GET") {
|
|
8674
|
+
const processes = await readRecentProcessMemoryDiagnostics(opts.globalRoot);
|
|
8494
8675
|
res.writeHead(200, {
|
|
8495
8676
|
"Content-Type": "application/json",
|
|
8496
8677
|
"Cache-Control": "no-store"
|
|
@@ -8503,6 +8684,7 @@ function createHttpServer(opts) {
|
|
|
8503
8684
|
uptime: process.uptime(),
|
|
8504
8685
|
cpuUsage: process.cpuUsage(),
|
|
8505
8686
|
codebaseIndexServer: getIndexState2().server,
|
|
8687
|
+
processes,
|
|
8506
8688
|
timestamp: Date.now()
|
|
8507
8689
|
})
|
|
8508
8690
|
);
|
|
@@ -8510,24 +8692,24 @@ function createHttpServer(opts) {
|
|
|
8510
8692
|
}
|
|
8511
8693
|
let filePath;
|
|
8512
8694
|
if (url.pathname === "/" || url.pathname === "") {
|
|
8513
|
-
filePath =
|
|
8695
|
+
filePath = path13.join(distDir, "index.html");
|
|
8514
8696
|
} else {
|
|
8515
|
-
filePath =
|
|
8697
|
+
filePath = path13.join(distDir, url.pathname);
|
|
8516
8698
|
}
|
|
8517
|
-
const resolvedPath =
|
|
8699
|
+
const resolvedPath = path13.resolve(filePath);
|
|
8518
8700
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
8519
8701
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
8520
8702
|
res.end("Forbidden");
|
|
8521
8703
|
return;
|
|
8522
8704
|
}
|
|
8523
|
-
const ext =
|
|
8705
|
+
const ext = path13.extname(resolvedPath);
|
|
8524
8706
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
8525
8707
|
res.setHeader("Content-Type", contentType);
|
|
8526
8708
|
setStaticSecurityHeaders(res);
|
|
8527
8709
|
if (ext === ".html") {
|
|
8528
8710
|
if (!shouldSetAuthCookie) res.setHeader("Cache-Control", "no-cache");
|
|
8529
8711
|
res.setHeader("Content-Security-Policy", buildCspHeader(opts.publicWsUrl, opts.host, port));
|
|
8530
|
-
const html = await
|
|
8712
|
+
const html = await fs10.readFile(resolvedPath, "utf8");
|
|
8531
8713
|
res.writeHead(200);
|
|
8532
8714
|
res.end(injectWsConfig(html, { publicWsUrl: opts.publicWsUrl }));
|
|
8533
8715
|
return;
|
|
@@ -8538,13 +8720,13 @@ function createHttpServer(opts) {
|
|
|
8538
8720
|
url.pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600"
|
|
8539
8721
|
);
|
|
8540
8722
|
}
|
|
8541
|
-
const fileContent = await
|
|
8723
|
+
const fileContent = await fs10.readFile(resolvedPath);
|
|
8542
8724
|
res.writeHead(200);
|
|
8543
8725
|
res.end(fileContent);
|
|
8544
8726
|
} catch (err) {
|
|
8545
8727
|
if (err.code === "ENOENT") {
|
|
8546
8728
|
try {
|
|
8547
|
-
const html = await
|
|
8729
|
+
const html = await fs10.readFile(path13.join(distDir, "index.html"), "utf8");
|
|
8548
8730
|
setStaticSecurityHeaders(res);
|
|
8549
8731
|
res.writeHead(200, {
|
|
8550
8732
|
"Content-Type": "text/html",
|
|
@@ -8574,14 +8756,14 @@ function createHttpServer(opts) {
|
|
|
8574
8756
|
|
|
8575
8757
|
// src/server/instance-registry.ts
|
|
8576
8758
|
import * as os from "node:os";
|
|
8577
|
-
import * as
|
|
8578
|
-
import * as
|
|
8759
|
+
import * as path14 from "node:path";
|
|
8760
|
+
import * as fs11 from "node:fs/promises";
|
|
8579
8761
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8580
8762
|
function defaultBaseDir() {
|
|
8581
|
-
return
|
|
8763
|
+
return path14.join(os.homedir(), ".wrongstack");
|
|
8582
8764
|
}
|
|
8583
8765
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8584
|
-
return
|
|
8766
|
+
return path14.join(baseDir, "webui-instances.json");
|
|
8585
8767
|
}
|
|
8586
8768
|
function isPidAlive(pid) {
|
|
8587
8769
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -8594,7 +8776,7 @@ function isPidAlive(pid) {
|
|
|
8594
8776
|
}
|
|
8595
8777
|
async function load(file) {
|
|
8596
8778
|
try {
|
|
8597
|
-
const raw = await
|
|
8779
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
8598
8780
|
const parsed = JSON.parse(raw);
|
|
8599
8781
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
8600
8782
|
return parsed;
|
|
@@ -10451,14 +10633,14 @@ function registerShutdownHandlers(res) {
|
|
|
10451
10633
|
}
|
|
10452
10634
|
|
|
10453
10635
|
// src/server/config-doctor.ts
|
|
10454
|
-
import * as
|
|
10636
|
+
import * as fs12 from "node:fs/promises";
|
|
10455
10637
|
import {
|
|
10456
10638
|
repairConfigDefaults
|
|
10457
10639
|
} from "@wrongstack/core/storage";
|
|
10458
10640
|
import { atomicWrite as atomicWrite5 } from "@wrongstack/core/utils";
|
|
10459
10641
|
import { decryptConfigSecrets } from "@wrongstack/core/security";
|
|
10460
10642
|
async function readConfig(file, vault) {
|
|
10461
|
-
const raw = await
|
|
10643
|
+
const raw = await fs12.readFile(file, "utf8");
|
|
10462
10644
|
const parsed = JSON.parse(raw);
|
|
10463
10645
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10464
10646
|
throw new Error("Active profile config must contain a JSON object.");
|
|
@@ -10522,6 +10704,7 @@ async function handleConfigDoctor(ws, apply, deps2) {
|
|
|
10522
10704
|
|
|
10523
10705
|
// src/server/mailbox-handlers.ts
|
|
10524
10706
|
import {
|
|
10707
|
+
actionToAckInput,
|
|
10525
10708
|
getSharedProjectMailbox,
|
|
10526
10709
|
isMailboxMessageVisibleTo,
|
|
10527
10710
|
MAILBOX_TYPE_PROPERTIES,
|
|
@@ -10538,6 +10721,43 @@ function getMailboxForDeps(deps2) {
|
|
|
10538
10721
|
const dir = resolveProjectDir(projectRoot, globalRoot);
|
|
10539
10722
|
return getSharedProjectMailbox(dir, deps2.events);
|
|
10540
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
|
+
}
|
|
10541
10761
|
async function handleMailboxSend(ws, deps2, payload) {
|
|
10542
10762
|
const mb = getMailboxForDeps(deps2);
|
|
10543
10763
|
if (!mb) {
|
|
@@ -10553,7 +10773,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10553
10773
|
}
|
|
10554
10774
|
try {
|
|
10555
10775
|
const message = await mb.send({
|
|
10556
|
-
from: "webui",
|
|
10776
|
+
from: payload.from ?? "webui",
|
|
10557
10777
|
to: payload.to,
|
|
10558
10778
|
type: payload.type,
|
|
10559
10779
|
audience: payload.audience,
|
|
@@ -10568,6 +10788,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10568
10788
|
requestId: payload.requestId,
|
|
10569
10789
|
success: true,
|
|
10570
10790
|
messageId: message.id,
|
|
10791
|
+
from: message.from,
|
|
10571
10792
|
to: message.to,
|
|
10572
10793
|
audience: message.audience ?? "all"
|
|
10573
10794
|
}
|
|
@@ -10612,6 +10833,7 @@ async function handleMailboxMessages(ws, deps2, payload) {
|
|
|
10612
10833
|
send(ws, {
|
|
10613
10834
|
type: "mailbox.messages",
|
|
10614
10835
|
payload: {
|
|
10836
|
+
...payload?.unreadOnly === true ? { unreadOnly: true } : {},
|
|
10615
10837
|
messages: visibleMessages.map((m) => {
|
|
10616
10838
|
const readByMe = payload?.agentId !== void 0 ? payload.agentId in m.readBy : false;
|
|
10617
10839
|
const completedByMe = payload?.agentId !== void 0 ? m.completedBy === payload.agentId : false;
|
|
@@ -10727,6 +10949,14 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10727
10949
|
...ctx.events ? { events: ctx.events } : {}
|
|
10728
10950
|
};
|
|
10729
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
|
+
},
|
|
10730
10960
|
send: (ws, msg) => {
|
|
10731
10961
|
const parsed = validateMailboxSendPayload(msg.payload);
|
|
10732
10962
|
if (!parsed.ok) {
|
|
@@ -10777,6 +11007,9 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10777
11007
|
}
|
|
10778
11008
|
async function handleMailboxRoute(ws, msg, handlers) {
|
|
10779
11009
|
switch (msg.type) {
|
|
11010
|
+
case "mailbox.action":
|
|
11011
|
+
await handlers.action(ws, msg);
|
|
11012
|
+
return true;
|
|
10780
11013
|
case "mailbox.send":
|
|
10781
11014
|
await handlers.send(ws, msg);
|
|
10782
11015
|
return true;
|
|
@@ -12090,10 +12323,10 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
12090
12323
|
import { spawn as spawn2 } from "node:child_process";
|
|
12091
12324
|
import { existsSync } from "node:fs";
|
|
12092
12325
|
import { findPackageJSON } from "node:module";
|
|
12093
|
-
import * as
|
|
12326
|
+
import * as path15 from "node:path";
|
|
12094
12327
|
function resolveDistDir(input) {
|
|
12095
12328
|
const options = typeof input === "string" ? { explicitDistDir: input } : input ?? {};
|
|
12096
|
-
if (options.explicitDistDir) return
|
|
12329
|
+
if (options.explicitDistDir) return path15.resolve(options.explicitDistDir);
|
|
12097
12330
|
const exists = options.exists ?? existsSync;
|
|
12098
12331
|
let packageTarget;
|
|
12099
12332
|
try {
|
|
@@ -12105,15 +12338,15 @@ function resolveDistDir(input) {
|
|
|
12105
12338
|
);
|
|
12106
12339
|
}
|
|
12107
12340
|
if (!packageTarget) return null;
|
|
12108
|
-
const distDir =
|
|
12341
|
+
const distDir = path15.basename(packageTarget) === "package.json" ? path15.join(path15.dirname(packageTarget), "dist") : path15.dirname(packageTarget);
|
|
12109
12342
|
if (options.exists === void 0 && options.resolvePackageJson) return distDir;
|
|
12110
|
-
return exists(
|
|
12343
|
+
return exists(path15.join(distDir, "index.html")) ? distDir : null;
|
|
12111
12344
|
}
|
|
12112
12345
|
async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
12113
12346
|
const exists = deps2.exists ?? existsSync;
|
|
12114
12347
|
if (explicitDistDir) {
|
|
12115
|
-
const resolved2 =
|
|
12116
|
-
return exists(
|
|
12348
|
+
const resolved2 = path15.resolve(explicitDistDir);
|
|
12349
|
+
return exists(path15.join(resolved2, "index.html")) ? resolved2 : null;
|
|
12117
12350
|
}
|
|
12118
12351
|
const resolveOptions = {
|
|
12119
12352
|
resolvePackageJson: deps2.resolvePackageJson,
|
|
@@ -12125,7 +12358,7 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
12125
12358
|
try {
|
|
12126
12359
|
const packageJson = deps2.resolvePackageJson ? deps2.resolvePackageJson("@wrongstack/webui/package.json") : findPackageJSON("@wrongstack/webui", import.meta.url);
|
|
12127
12360
|
if (!packageJson) throw new Error("not found");
|
|
12128
|
-
packageDir =
|
|
12361
|
+
packageDir = path15.dirname(packageJson);
|
|
12129
12362
|
} catch {
|
|
12130
12363
|
throw new Error(
|
|
12131
12364
|
"@wrongstack/webui package could not be resolved. Install workspace dependencies and rebuild the CLI."
|
|
@@ -12134,8 +12367,8 @@ async function ensureDistDir(explicitDistDir, deps2 = {}) {
|
|
|
12134
12367
|
const findRoot = deps2.findWorkspaceRoot ?? ((pkgDir) => {
|
|
12135
12368
|
let dir = pkgDir;
|
|
12136
12369
|
for (let i = 0; i < 10; i++) {
|
|
12137
|
-
if (existsSync(
|
|
12138
|
-
const parent =
|
|
12370
|
+
if (existsSync(path15.join(dir, "pnpm-workspace.yaml"))) return dir;
|
|
12371
|
+
const parent = path15.dirname(dir);
|
|
12139
12372
|
if (parent === dir) return null;
|
|
12140
12373
|
dir = parent;
|
|
12141
12374
|
}
|
|
@@ -12219,7 +12452,7 @@ function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
|
12219
12452
|
}
|
|
12220
12453
|
|
|
12221
12454
|
// src/server/embedded-lifecycle.ts
|
|
12222
|
-
import * as
|
|
12455
|
+
import * as path16 from "node:path";
|
|
12223
12456
|
|
|
12224
12457
|
// src/server/network-info.ts
|
|
12225
12458
|
import * as os2 from "node:os";
|
|
@@ -12285,7 +12518,7 @@ function registerWebuiInstance(p, deps2 = {}) {
|
|
|
12285
12518
|
httpPort: p.httpPort,
|
|
12286
12519
|
host: p.host,
|
|
12287
12520
|
projectRoot: p.projectRoot,
|
|
12288
|
-
projectName:
|
|
12521
|
+
projectName: path16.basename(p.projectRoot) || p.projectRoot,
|
|
12289
12522
|
startedAt: p.startedAt,
|
|
12290
12523
|
url: buildWebUIAccessUrl({
|
|
12291
12524
|
host: p.host,
|
|
@@ -12336,6 +12569,7 @@ function createWebuiShutdown(res) {
|
|
|
12336
12569
|
log("[WebUI] Shutting down...");
|
|
12337
12570
|
res.abortInFlight();
|
|
12338
12571
|
res.unsubscribeEvents();
|
|
12572
|
+
res.disposeResources?.();
|
|
12339
12573
|
res.closeClients();
|
|
12340
12574
|
const unregistered = unregister(res.pid, res.registryBaseDir).catch(
|
|
12341
12575
|
(err) => debug(`[webui-server] unregister failed: ${err}`)
|
|
@@ -12505,7 +12739,7 @@ ${text2}` : text2;
|
|
|
12505
12739
|
|
|
12506
12740
|
// src/server/client-presence.ts
|
|
12507
12741
|
import * as crypto2 from "node:crypto";
|
|
12508
|
-
import * as
|
|
12742
|
+
import * as path17 from "node:path";
|
|
12509
12743
|
import {
|
|
12510
12744
|
getSharedProjectMailbox as getSharedProjectMailbox2,
|
|
12511
12745
|
resolveProjectDir as resolveProjectDir2
|
|
@@ -12523,7 +12757,7 @@ function createWebuiClientPresence(deps2) {
|
|
|
12523
12757
|
if (!deps2.projectRoot) return null;
|
|
12524
12758
|
try {
|
|
12525
12759
|
const projectRoot = deps2.projectRoot;
|
|
12526
|
-
const projectName =
|
|
12760
|
+
const projectName = path17.basename(projectRoot);
|
|
12527
12761
|
const nextMailbox = getSharedProjectMailbox2(
|
|
12528
12762
|
resolveProjectDir2(projectRoot, wstackGlobalRoot()),
|
|
12529
12763
|
deps2.events,
|
|
@@ -13142,6 +13376,26 @@ function createKanbanSupervisor(deps2) {
|
|
|
13142
13376
|
const agentRunning = /* @__PURE__ */ new Set();
|
|
13143
13377
|
let disposed = false;
|
|
13144
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
|
+
};
|
|
13145
13399
|
const publish = (snapshot) => {
|
|
13146
13400
|
snapshots.set(snapshot.boardId, snapshot);
|
|
13147
13401
|
deps2.broadcast({
|
|
@@ -13241,6 +13495,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
13241
13495
|
onDone: async (result) => {
|
|
13242
13496
|
clearTimeout(watchdog);
|
|
13243
13497
|
agentRunning.delete(board.id);
|
|
13498
|
+
if (await getBoard3(deps2.projectRoot, board.id) === null) return;
|
|
13244
13499
|
const current3 = snapshots.get(board.id) ?? snapshot;
|
|
13245
13500
|
publish({
|
|
13246
13501
|
...current3,
|
|
@@ -13262,13 +13517,20 @@ function createKanbanSupervisor(deps2) {
|
|
|
13262
13517
|
}
|
|
13263
13518
|
};
|
|
13264
13519
|
const auditNow = async (boardId) => {
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
(
|
|
13269
|
-
(
|
|
13270
|
-
|
|
13271
|
-
|
|
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
|
+
}
|
|
13272
13534
|
const results = [];
|
|
13273
13535
|
for (const board of boards) results.push(await auditBoard(board));
|
|
13274
13536
|
scheduleNext();
|
|
@@ -13303,6 +13565,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
13303
13565
|
try {
|
|
13304
13566
|
const now = Date.now();
|
|
13305
13567
|
const summaries = await listBoards4(deps2.projectRoot);
|
|
13568
|
+
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
13306
13569
|
for (const summary of summaries) {
|
|
13307
13570
|
if ((nextDue.get(summary.id) ?? 0) > now) continue;
|
|
13308
13571
|
const board = await getBoard3(deps2.projectRoot, summary.id);
|
|
@@ -13318,12 +13581,22 @@ function createKanbanSupervisor(deps2) {
|
|
|
13318
13581
|
return {
|
|
13319
13582
|
getSnapshot: (boardId) => snapshots.get(boardId),
|
|
13320
13583
|
auditNow,
|
|
13584
|
+
getStats: () => ({
|
|
13585
|
+
snapshots: snapshots.size,
|
|
13586
|
+
scheduledBoards: nextDue.size,
|
|
13587
|
+
agentCooldowns: agentLastRun.size,
|
|
13588
|
+
runningAgents: agentRunning.size
|
|
13589
|
+
}),
|
|
13321
13590
|
dispose() {
|
|
13322
13591
|
disposed = true;
|
|
13323
13592
|
if (nextTimer !== void 0) {
|
|
13324
13593
|
clearTimeout(nextTimer);
|
|
13325
13594
|
nextTimer = void 0;
|
|
13326
13595
|
}
|
|
13596
|
+
snapshots.clear();
|
|
13597
|
+
nextDue.clear();
|
|
13598
|
+
agentLastRun.clear();
|
|
13599
|
+
agentRunning.clear();
|
|
13327
13600
|
}
|
|
13328
13601
|
};
|
|
13329
13602
|
}
|
|
@@ -13500,8 +13773,8 @@ function seedContextMeta(config, context) {
|
|
|
13500
13773
|
}
|
|
13501
13774
|
|
|
13502
13775
|
// src/server/pref-helpers.ts
|
|
13503
|
-
import * as
|
|
13504
|
-
import * as
|
|
13776
|
+
import * as fs13 from "node:fs/promises";
|
|
13777
|
+
import * as path18 from "node:path";
|
|
13505
13778
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
13506
13779
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
13507
13780
|
var PREF_KEYS = [
|
|
@@ -13597,11 +13870,11 @@ function prefSnapshot(contextMeta) {
|
|
|
13597
13870
|
return snapshot;
|
|
13598
13871
|
}
|
|
13599
13872
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
13600
|
-
const globalRoot =
|
|
13873
|
+
const globalRoot = path18.dirname(filePath);
|
|
13601
13874
|
await backupConfigFile(filePath, { globalRoot });
|
|
13602
13875
|
let raw;
|
|
13603
13876
|
try {
|
|
13604
|
-
raw = await
|
|
13877
|
+
raw = await fs13.readFile(filePath, "utf8");
|
|
13605
13878
|
} catch {
|
|
13606
13879
|
raw = "{}";
|
|
13607
13880
|
}
|
|
@@ -14076,16 +14349,16 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
14076
14349
|
}
|
|
14077
14350
|
|
|
14078
14351
|
// src/server/embedded-host-adapters.ts
|
|
14079
|
-
import * as
|
|
14080
|
-
import * as
|
|
14352
|
+
import * as fs16 from "node:fs/promises";
|
|
14353
|
+
import * as path20 from "node:path";
|
|
14081
14354
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
14082
14355
|
import { DefaultSessionStore as DefaultSessionStore2 } from "@wrongstack/core/storage";
|
|
14083
14356
|
import { toErrorMessage as toErrorMessage7, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
|
|
14084
14357
|
import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
14085
14358
|
|
|
14086
14359
|
// src/server/project-handlers.ts
|
|
14087
|
-
import * as
|
|
14088
|
-
import * as
|
|
14360
|
+
import * as fs14 from "node:fs/promises";
|
|
14361
|
+
import * as path19 from "node:path";
|
|
14089
14362
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
14090
14363
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
14091
14364
|
function createProjectHandlers(ctx) {
|
|
@@ -14137,10 +14410,10 @@ function createProjectHandlers(ctx) {
|
|
|
14137
14410
|
});
|
|
14138
14411
|
return;
|
|
14139
14412
|
}
|
|
14140
|
-
const resolved =
|
|
14141
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14413
|
+
const resolved = path19.resolve(parsed.value.root);
|
|
14414
|
+
const name2 = parsed.value.name?.trim() || path19.basename(resolved);
|
|
14142
14415
|
try {
|
|
14143
|
-
const stat3 = await
|
|
14416
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
14144
14417
|
if (!stat3?.isDirectory()) {
|
|
14145
14418
|
sendTo(ws, {
|
|
14146
14419
|
type: "projects.added",
|
|
@@ -14149,7 +14422,7 @@ function createProjectHandlers(ctx) {
|
|
|
14149
14422
|
return;
|
|
14150
14423
|
}
|
|
14151
14424
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
14152
|
-
const already = before.projects.some((project) =>
|
|
14425
|
+
const already = before.projects.some((project) => path19.resolve(project.root) === resolved);
|
|
14153
14426
|
const entry = await touchProjectInManifest(
|
|
14154
14427
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
14155
14428
|
ctx.globalConfigPath
|
|
@@ -14179,8 +14452,8 @@ function createProjectHandlers(ctx) {
|
|
|
14179
14452
|
});
|
|
14180
14453
|
return;
|
|
14181
14454
|
}
|
|
14182
|
-
const resolved =
|
|
14183
|
-
const name2 = parsed.value.name?.trim() ||
|
|
14455
|
+
const resolved = path19.resolve(parsed.value.root);
|
|
14456
|
+
const name2 = parsed.value.name?.trim() || path19.basename(resolved);
|
|
14184
14457
|
if (!ctx.allowProjectMutations) {
|
|
14185
14458
|
sendTo(ws, {
|
|
14186
14459
|
type: "projects.selected",
|
|
@@ -14193,7 +14466,7 @@ function createProjectHandlers(ctx) {
|
|
|
14193
14466
|
return;
|
|
14194
14467
|
}
|
|
14195
14468
|
try {
|
|
14196
|
-
const stat3 = await
|
|
14469
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
14197
14470
|
if (!stat3?.isDirectory()) {
|
|
14198
14471
|
sendTo(ws, {
|
|
14199
14472
|
type: "projects.selected",
|
|
@@ -14223,7 +14496,7 @@ function createProjectHandlers(ctx) {
|
|
|
14223
14496
|
const previousIdentityTarget = {
|
|
14224
14497
|
projectSlug: previousPaths.projectSlug,
|
|
14225
14498
|
projectRoot: previousProjectRoot,
|
|
14226
|
-
projectName:
|
|
14499
|
+
projectName: path19.basename(previousProjectRoot),
|
|
14227
14500
|
workingDir: ctx.context.workingDir
|
|
14228
14501
|
};
|
|
14229
14502
|
const previousUsage = ctx.tokenCounter.total();
|
|
@@ -14386,14 +14659,14 @@ async function searchCatalogModels(registry, rawQuery, limit = 8) {
|
|
|
14386
14659
|
}
|
|
14387
14660
|
|
|
14388
14661
|
// src/server/provider-config-io.ts
|
|
14389
|
-
import * as
|
|
14662
|
+
import * as fs15 from "node:fs/promises";
|
|
14390
14663
|
import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
|
|
14391
14664
|
import { atomicWrite as atomicWrite7 } from "@wrongstack/core/utils";
|
|
14392
14665
|
import { decryptConfigSecrets as decryptConfigSecrets3, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
14393
14666
|
async function loadSavedProviders(configPath, vault) {
|
|
14394
14667
|
let raw;
|
|
14395
14668
|
try {
|
|
14396
|
-
raw = await
|
|
14669
|
+
raw = await fs15.readFile(configPath, "utf8");
|
|
14397
14670
|
} catch {
|
|
14398
14671
|
return {};
|
|
14399
14672
|
}
|
|
@@ -14411,7 +14684,7 @@ async function saveProviders(configPath, vault, providers, profileConfigPath) {
|
|
|
14411
14684
|
let raw;
|
|
14412
14685
|
let fileExists = true;
|
|
14413
14686
|
try {
|
|
14414
|
-
raw = await
|
|
14687
|
+
raw = await fs15.readFile(targetPath, "utf8");
|
|
14415
14688
|
} catch (err) {
|
|
14416
14689
|
if (err.code !== "ENOENT") {
|
|
14417
14690
|
throw new ConfigError2({
|
|
@@ -15177,6 +15450,7 @@ var CLIENT_COLLABORATION_MESSAGE_TYPES = [
|
|
|
15177
15450
|
"collab.resume",
|
|
15178
15451
|
"collab.grant_control",
|
|
15179
15452
|
"collab.inject_tool",
|
|
15453
|
+
"mailbox.action",
|
|
15180
15454
|
"mailbox.agents",
|
|
15181
15455
|
"mailbox.clear",
|
|
15182
15456
|
"mailbox.compact",
|
|
@@ -15442,6 +15716,7 @@ var SERVER_COLLABORATION_MESSAGE_TYPES = [
|
|
|
15442
15716
|
"collab.pause.granted",
|
|
15443
15717
|
"collab.pause.released",
|
|
15444
15718
|
"collab.state",
|
|
15719
|
+
"mailbox.action_result",
|
|
15445
15720
|
"mailbox.agent_registered",
|
|
15446
15721
|
"mailbox.agents",
|
|
15447
15722
|
"mailbox.cleared",
|
|
@@ -15665,13 +15940,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
15665
15940
|
// src/protocol/decoder.ts
|
|
15666
15941
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
15667
15942
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
15668
|
-
function inspectValue(value,
|
|
15943
|
+
function inspectValue(value, path35, depth) {
|
|
15669
15944
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
15670
|
-
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 };
|
|
15671
15946
|
}
|
|
15672
15947
|
if (value === null || typeof value !== "object") return null;
|
|
15673
15948
|
for (const key of Object.keys(value)) {
|
|
15674
|
-
const childPath = `${
|
|
15949
|
+
const childPath = `${path35}.${key}`;
|
|
15675
15950
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
15676
15951
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
15677
15952
|
}
|
|
@@ -15768,7 +16043,11 @@ function projectChatMessage(message) {
|
|
|
15768
16043
|
return value ? { kind: "text-delta", text: value, messageId: text(payload["messageId"]) } : null;
|
|
15769
16044
|
}
|
|
15770
16045
|
case "provider.response":
|
|
15771
|
-
return {
|
|
16046
|
+
return {
|
|
16047
|
+
kind: "response",
|
|
16048
|
+
content: payload["content"],
|
|
16049
|
+
stopReason: text(payload["stopReason"])
|
|
16050
|
+
};
|
|
15772
16051
|
case "run.result":
|
|
15773
16052
|
return {
|
|
15774
16053
|
kind: "run-result",
|
|
@@ -16605,7 +16884,7 @@ function createEmbeddedConversationRoutes(ctx) {
|
|
|
16605
16884
|
}
|
|
16606
16885
|
function sessionStoreFor(opts) {
|
|
16607
16886
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
16608
|
-
return opts.sessionStore ?? new DefaultSessionStore2({ dir:
|
|
16887
|
+
return opts.sessionStore ?? new DefaultSessionStore2({ dir: path20.join(projectRoot, ".wrongstack", "sessions"), projectRoot });
|
|
16609
16888
|
}
|
|
16610
16889
|
function createEmbeddedSessionRoutes(ctx) {
|
|
16611
16890
|
const { opts } = ctx;
|
|
@@ -16624,7 +16903,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16624
16903
|
getSession: () => actx.session ?? opts.session,
|
|
16625
16904
|
getSessionStore: () => sessionStoreFor(opts),
|
|
16626
16905
|
canSwapSessions: () => opts.sessionStore !== void 0,
|
|
16627
|
-
getSessionsDir: () => opts.sessionsDir ??
|
|
16906
|
+
getSessionsDir: () => opts.sessionsDir ?? path20.join(getProjectRoot(), ".wrongstack", "sessions"),
|
|
16628
16907
|
setSession: (next) => {
|
|
16629
16908
|
actx.session = next;
|
|
16630
16909
|
},
|
|
@@ -16641,7 +16920,7 @@ function createEmbeddedSessionRoutes(ctx) {
|
|
|
16641
16920
|
async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
16642
16921
|
const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
16643
16922
|
try {
|
|
16644
|
-
const raw = await
|
|
16923
|
+
const raw = await fs16.readFile(path20.join(projectRoot, ".wrongstack", "goal.json"), "utf8");
|
|
16645
16924
|
ctx.broadcast({ type: "goal-state.updated", payload: JSON.parse(raw) });
|
|
16646
16925
|
} catch {
|
|
16647
16926
|
ctx.broadcast({ type: "goal-state.updated", payload: null });
|
|
@@ -16650,10 +16929,10 @@ async function broadcastEmbeddedGoalSnapshot(ctx) {
|
|
|
16650
16929
|
function createEmbeddedProjectRoutes(ctx) {
|
|
16651
16930
|
const { opts } = ctx;
|
|
16652
16931
|
const actx = opts.agent.ctx;
|
|
16653
|
-
const globalConfigPath = opts.globalConfigPath ??
|
|
16932
|
+
const globalConfigPath = opts.globalConfigPath ?? path20.join(wstackGlobalRoot2(), "config.json");
|
|
16654
16933
|
return createProjectHandlers({
|
|
16655
16934
|
globalConfigPath,
|
|
16656
|
-
wpaths: { globalRoot:
|
|
16935
|
+
wpaths: { globalRoot: path20.dirname(globalConfigPath) },
|
|
16657
16936
|
context: actx,
|
|
16658
16937
|
tokenCounter: actx.tokenCounter,
|
|
16659
16938
|
config: { model: actx.model, provider: actx.provider.id },
|
|
@@ -17701,8 +17980,8 @@ function createRouteFamilyDispatcher(options) {
|
|
|
17701
17980
|
}
|
|
17702
17981
|
|
|
17703
17982
|
// src/server/shell-open.ts
|
|
17704
|
-
import * as
|
|
17705
|
-
import * as
|
|
17983
|
+
import * as fs17 from "node:fs/promises";
|
|
17984
|
+
import * as path21 from "node:path";
|
|
17706
17985
|
import { spawn as spawn3 } from "node:child_process";
|
|
17707
17986
|
function normalizeShellOpenTarget(target) {
|
|
17708
17987
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -17713,11 +17992,11 @@ function shellQuote(s) {
|
|
|
17713
17992
|
}
|
|
17714
17993
|
async function handleShellOpen(req, logger, options) {
|
|
17715
17994
|
try {
|
|
17716
|
-
const resolved =
|
|
17995
|
+
const resolved = path21.resolve(req.path);
|
|
17717
17996
|
if (options?.projectRoot) {
|
|
17718
|
-
const root =
|
|
17719
|
-
const relative5 =
|
|
17720
|
-
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);
|
|
17721
18000
|
if (escapes) {
|
|
17722
18001
|
return {
|
|
17723
18002
|
success: false,
|
|
@@ -17725,7 +18004,7 @@ async function handleShellOpen(req, logger, options) {
|
|
|
17725
18004
|
};
|
|
17726
18005
|
}
|
|
17727
18006
|
}
|
|
17728
|
-
await
|
|
18007
|
+
await fs17.access(resolved);
|
|
17729
18008
|
if (METACHAR_REGEX.test(resolved)) {
|
|
17730
18009
|
return { success: false, message: "Path contains unsupported characters." };
|
|
17731
18010
|
}
|
|
@@ -18129,7 +18408,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
18129
18408
|
}
|
|
18130
18409
|
|
|
18131
18410
|
// src/server/provider-config-standalone.ts
|
|
18132
|
-
import * as
|
|
18411
|
+
import * as path22 from "node:path";
|
|
18133
18412
|
import { DefaultSecretVault } from "@wrongstack/core/security";
|
|
18134
18413
|
function createProviderConfigIO(configPath) {
|
|
18135
18414
|
const keyFile = vaultKeyFileForConfigPath(configPath);
|
|
@@ -18140,15 +18419,15 @@ function createProviderConfigIO(configPath) {
|
|
|
18140
18419
|
};
|
|
18141
18420
|
}
|
|
18142
18421
|
function vaultKeyFileForConfigPath(configPath) {
|
|
18143
|
-
const configDir =
|
|
18144
|
-
const parentDir =
|
|
18145
|
-
const globalRoot =
|
|
18146
|
-
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");
|
|
18147
18426
|
}
|
|
18148
18427
|
|
|
18149
18428
|
// src/server/provider-store.ts
|
|
18150
18429
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
18151
|
-
import * as
|
|
18430
|
+
import * as fs18 from "node:fs/promises";
|
|
18152
18431
|
import { decryptConfigSecrets as decryptConfigSecrets4, encryptConfigSecrets as encryptConfigSecrets3 } from "@wrongstack/core/security";
|
|
18153
18432
|
import { atomicWrite as atomicWrite8 } from "@wrongstack/core/utils";
|
|
18154
18433
|
function createConfigWriteLock() {
|
|
@@ -18172,7 +18451,7 @@ function createProviderStore(deps2) {
|
|
|
18172
18451
|
const configWriteLock = createConfigWriteLock();
|
|
18173
18452
|
async function loadSavedProviders2() {
|
|
18174
18453
|
try {
|
|
18175
|
-
const raw = await
|
|
18454
|
+
const raw = await fs18.readFile(profileConfigPath, "utf8");
|
|
18176
18455
|
const parsed = JSON.parse(raw);
|
|
18177
18456
|
if (!parsed.providers) return {};
|
|
18178
18457
|
return decryptConfigSecrets4(parsed.providers, vault);
|
|
@@ -18186,7 +18465,7 @@ function createProviderStore(deps2) {
|
|
|
18186
18465
|
await prev;
|
|
18187
18466
|
let parsed;
|
|
18188
18467
|
try {
|
|
18189
|
-
const raw = await
|
|
18468
|
+
const raw = await fs18.readFile(profileConfigPath, "utf8");
|
|
18190
18469
|
parsed = JSON.parse(raw);
|
|
18191
18470
|
} catch {
|
|
18192
18471
|
parsed = {};
|
|
@@ -18236,7 +18515,12 @@ function createProviderStore(deps2) {
|
|
|
18236
18515
|
}
|
|
18237
18516
|
|
|
18238
18517
|
// src/server/sdd-board-ws-handler.ts
|
|
18239
|
-
import {
|
|
18518
|
+
import {
|
|
18519
|
+
enqueueKanbanWorkflowCommand,
|
|
18520
|
+
kanbanWorkflowId,
|
|
18521
|
+
listBoards as listBoards5,
|
|
18522
|
+
listKanbanWorkflowStates
|
|
18523
|
+
} from "@wrongstack/kanban";
|
|
18240
18524
|
import {
|
|
18241
18525
|
applySddLifecycle,
|
|
18242
18526
|
extractVerificationCommand,
|
|
@@ -18263,7 +18547,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18263
18547
|
clients = /* @__PURE__ */ new Set();
|
|
18264
18548
|
lifecycle;
|
|
18265
18549
|
security;
|
|
18266
|
-
|
|
18550
|
+
standalonePollingEnabled;
|
|
18267
18551
|
latest = null;
|
|
18268
18552
|
poll = null;
|
|
18269
18553
|
pollInFlight = false;
|
|
@@ -18272,7 +18556,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18272
18556
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
18273
18557
|
this.lifecycle = lifecycle;
|
|
18274
18558
|
this.security = security;
|
|
18275
|
-
this.
|
|
18559
|
+
this.standalonePollingEnabled = events === void 0;
|
|
18276
18560
|
if (events) {
|
|
18277
18561
|
const handler = (e) => {
|
|
18278
18562
|
this.latest = e.snapshot;
|
|
@@ -18307,7 +18591,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18307
18591
|
return;
|
|
18308
18592
|
}
|
|
18309
18593
|
if (msg.type === "sdd.board.list") {
|
|
18310
|
-
const boards = await this.
|
|
18594
|
+
const boards = await this.listBoardEntries();
|
|
18311
18595
|
this.broadcast({ type: "sdd.board.list", payload: { boards } });
|
|
18312
18596
|
return;
|
|
18313
18597
|
}
|
|
@@ -18320,7 +18604,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
18320
18604
|
const verificationCommands = [];
|
|
18321
18605
|
if (action === "set_task_verification") {
|
|
18322
18606
|
const command = msg.payload?.verificationCommand;
|
|
18323
|
-
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
18607
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
18608
|
+
return;
|
|
18324
18609
|
if (typeof command === "string" && command.trim()) {
|
|
18325
18610
|
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
18326
18611
|
}
|
|
@@ -18354,18 +18639,26 @@ var SddBoardWebSocketHandler = class {
|
|
|
18354
18639
|
);
|
|
18355
18640
|
if (!authorization.allowed) return;
|
|
18356
18641
|
}
|
|
18357
|
-
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.
|
|
18642
|
+
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.listBoardEntries())[0]?.runId;
|
|
18358
18643
|
if (runId) {
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18363
|
-
|
|
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
|
+
}
|
|
18364
18657
|
}
|
|
18365
18658
|
}
|
|
18366
18659
|
}
|
|
18367
18660
|
/**
|
|
18368
|
-
* Apply a cleanup/rollback/destroy from
|
|
18661
|
+
* Apply a cleanup/rollback/destroy from durable state and broadcast a structured
|
|
18369
18662
|
* `sdd.board.lifecycle_result`. Refuses (no-op) while a run is still active —
|
|
18370
18663
|
* the user must stop it first; the UI gates the buttons on `!active` and the
|
|
18371
18664
|
* Destroy flow auto-stops then waits before sending `destroy`.
|
|
@@ -18374,7 +18667,11 @@ var SddBoardWebSocketHandler = class {
|
|
|
18374
18667
|
if (!this.lifecycle) {
|
|
18375
18668
|
this.broadcast({
|
|
18376
18669
|
type: "sdd.board.lifecycle_result",
|
|
18377
|
-
payload: {
|
|
18670
|
+
payload: {
|
|
18671
|
+
op,
|
|
18672
|
+
ok: false,
|
|
18673
|
+
reason: "Lifecycle operations are not available in this session."
|
|
18674
|
+
}
|
|
18378
18675
|
});
|
|
18379
18676
|
return;
|
|
18380
18677
|
}
|
|
@@ -18390,6 +18687,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
18390
18687
|
projectRoot: this.lifecycle.projectRoot,
|
|
18391
18688
|
paths: this.lifecycle.paths,
|
|
18392
18689
|
runId,
|
|
18690
|
+
stateTransport: this.lifecycle.stateTransport ?? "kanban",
|
|
18393
18691
|
revertMerged: payload?.revertMerged === true
|
|
18394
18692
|
});
|
|
18395
18693
|
this.broadcast({ type: "sdd.board.lifecycle_result", payload: result });
|
|
@@ -18418,22 +18716,27 @@ var SddBoardWebSocketHandler = class {
|
|
|
18418
18716
|
if (this.pollInFlight) return;
|
|
18419
18717
|
this.pollInFlight = true;
|
|
18420
18718
|
try {
|
|
18421
|
-
const
|
|
18422
|
-
if (!
|
|
18423
|
-
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) {
|
|
18424
18722
|
return;
|
|
18425
18723
|
}
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
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
|
+
);
|
|
18431
18734
|
} finally {
|
|
18432
18735
|
this.pollInFlight = false;
|
|
18433
18736
|
}
|
|
18434
18737
|
}
|
|
18435
18738
|
startPolling() {
|
|
18436
|
-
if (!this.
|
|
18739
|
+
if (!this.standalonePollingEnabled || this.poll !== null || this.clients.size === 0) return;
|
|
18437
18740
|
this.poll = setInterval(() => void this.pollLatest(), 1e3);
|
|
18438
18741
|
this.poll.unref?.();
|
|
18439
18742
|
}
|
|
@@ -18443,17 +18746,44 @@ var SddBoardWebSocketHandler = class {
|
|
|
18443
18746
|
this.poll = null;
|
|
18444
18747
|
}
|
|
18445
18748
|
async sendCurrent(client) {
|
|
18446
|
-
const snap = this.latest ?? await this.
|
|
18749
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
18447
18750
|
if (snap) this.send(client, { type: "sdd.board.snapshot", payload: snap });
|
|
18448
18751
|
}
|
|
18449
18752
|
async broadcastCurrent() {
|
|
18450
|
-
const snap = this.latest ?? await this.
|
|
18753
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
18451
18754
|
if (snap) this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
18452
18755
|
}
|
|
18453
|
-
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
|
+
}
|
|
18454
18762
|
const entry = await this.store.latest();
|
|
18455
18763
|
return entry ? this.store.load(entry.runId) : null;
|
|
18456
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
|
+
}
|
|
18457
18787
|
broadcast(msg) {
|
|
18458
18788
|
const data = JSON.stringify(msg);
|
|
18459
18789
|
for (const client of this.clients) {
|
|
@@ -18464,17 +18794,20 @@ var SddBoardWebSocketHandler = class {
|
|
|
18464
18794
|
sendSerialized(client.ws, JSON.stringify(msg));
|
|
18465
18795
|
}
|
|
18466
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
|
+
}
|
|
18467
18802
|
|
|
18468
18803
|
// src/server/sdd-wizard-wiring.ts
|
|
18469
|
-
import * as
|
|
18470
|
-
import {
|
|
18471
|
-
DefaultTaskStore,
|
|
18472
|
-
TaskTracker
|
|
18473
|
-
} from "@wrongstack/core/tasking";
|
|
18804
|
+
import * as path23 from "node:path";
|
|
18805
|
+
import { DefaultTaskStore, TaskTracker } from "@wrongstack/core/tasking";
|
|
18474
18806
|
import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
18475
18807
|
import { WorktreeManager as WorktreeManager2 } from "@wrongstack/core/worktree";
|
|
18476
18808
|
import {
|
|
18477
18809
|
cleanupStaleSddWorktrees,
|
|
18810
|
+
createKanbanSddSessionPersistence,
|
|
18478
18811
|
decomposeNonAtomicTasks,
|
|
18479
18812
|
gatherProjectContext,
|
|
18480
18813
|
makeAcceptanceCriteriaVerifier,
|
|
@@ -18492,7 +18825,7 @@ import {
|
|
|
18492
18825
|
} from "@wrongstack/sdd";
|
|
18493
18826
|
async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
18494
18827
|
const runTracker = tracker ?? (() => {
|
|
18495
|
-
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
18828
|
+
const t = new TaskTracker({ store: deps2.taskStore ?? new DefaultTaskStore() });
|
|
18496
18829
|
t.setGraph(graph);
|
|
18497
18830
|
return t;
|
|
18498
18831
|
})();
|
|
@@ -18501,7 +18834,8 @@ async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
|
18501
18834
|
if (worktreesEnabled && await isGitWorkTree(deps2.projectRoot)) {
|
|
18502
18835
|
void cleanupStaleSddWorktrees({
|
|
18503
18836
|
projectRoot: deps2.projectRoot,
|
|
18504
|
-
boardsDir: deps2.projectSddBoards
|
|
18837
|
+
boardsDir: deps2.projectSddBoards,
|
|
18838
|
+
stateTransport: "kanban"
|
|
18505
18839
|
}).catch(() => void 0);
|
|
18506
18840
|
worktrees = new WorktreeManager2({
|
|
18507
18841
|
projectRoot: deps2.projectRoot,
|
|
@@ -18580,7 +18914,8 @@ function buildSddWizardDeps(opts) {
|
|
|
18580
18914
|
}).catch(() => {
|
|
18581
18915
|
projectContext = "";
|
|
18582
18916
|
});
|
|
18583
|
-
const
|
|
18917
|
+
const legacySessionPath = opts.paths.projectSddSession ?? path23.join(opts.paths.projectDir, "sdd-session.json");
|
|
18918
|
+
const sessionPersistence = createKanbanSddSessionPersistence(opts.projectRoot, legacySessionPath);
|
|
18584
18919
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
18585
18920
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
18586
18921
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -18601,7 +18936,7 @@ function buildSddWizardDeps(opts) {
|
|
|
18601
18936
|
const makeDriver = () => new SddInterviewDriver({
|
|
18602
18937
|
specStore,
|
|
18603
18938
|
graphStore,
|
|
18604
|
-
|
|
18939
|
+
sessionPersistence,
|
|
18605
18940
|
projectContext
|
|
18606
18941
|
});
|
|
18607
18942
|
const launchFromGraph = async (graph, config, tracker) => {
|
|
@@ -18613,6 +18948,7 @@ function buildSddWizardDeps(opts) {
|
|
|
18613
18948
|
projectRoot: opts.projectRoot,
|
|
18614
18949
|
subagentFactory: opts.subagentFactory,
|
|
18615
18950
|
projectSddBoards: opts.paths.projectSddBoards,
|
|
18951
|
+
taskStore: graphStore,
|
|
18616
18952
|
registry,
|
|
18617
18953
|
runIsolatedTurn,
|
|
18618
18954
|
...opts.brain ? { brain: opts.brain } : {}
|
|
@@ -18682,8 +19018,18 @@ var SddWizardWebSocketHandler = class {
|
|
|
18682
19018
|
lastAgentText = "";
|
|
18683
19019
|
/** Guards against overlapping interview turns (one in flight at a time). */
|
|
18684
19020
|
busy = false;
|
|
18685
|
-
/**
|
|
19021
|
+
/** Set when authoritative session state could not be read during bootstrap. */
|
|
19022
|
+
resumeError = null;
|
|
19023
|
+
/** Resolves once project-context gather + durable resume finish. */
|
|
18686
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;
|
|
18687
19033
|
async bootstrap() {
|
|
18688
19034
|
try {
|
|
18689
19035
|
await this.deps.ensureReady?.();
|
|
@@ -18691,7 +19037,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
18691
19037
|
}
|
|
18692
19038
|
await this.tryResume();
|
|
18693
19039
|
}
|
|
18694
|
-
/** Rehydrate a persisted interview if one exists
|
|
19040
|
+
/** Rehydrate a persisted interview if one exists. */
|
|
18695
19041
|
async tryResume() {
|
|
18696
19042
|
if (this.driver) return;
|
|
18697
19043
|
try {
|
|
@@ -18700,9 +19046,11 @@ var SddWizardWebSocketHandler = class {
|
|
|
18700
19046
|
this.driver = driver;
|
|
18701
19047
|
this.lastAgentText = driver.getLastAgentText() ?? "";
|
|
18702
19048
|
}
|
|
18703
|
-
|
|
19049
|
+
this.resumeError = null;
|
|
19050
|
+
} catch (error2) {
|
|
18704
19051
|
this.driver = null;
|
|
18705
19052
|
this.lastAgentText = "";
|
|
19053
|
+
this.resumeError = error2 instanceof Error ? error2.message : String(error2);
|
|
18706
19054
|
}
|
|
18707
19055
|
}
|
|
18708
19056
|
addClient(ws) {
|
|
@@ -18711,6 +19059,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
18711
19059
|
ws.on("close", () => this.clients.delete(client));
|
|
18712
19060
|
ws.on("error", () => this.clients.delete(client));
|
|
18713
19061
|
void this.ready.then(() => {
|
|
19062
|
+
if (this.resumeError) {
|
|
19063
|
+
this.send(client, { type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
19064
|
+
return;
|
|
19065
|
+
}
|
|
18714
19066
|
if (this.driver) {
|
|
18715
19067
|
this.send(client, this.snapshotMsg());
|
|
18716
19068
|
if (this.lastAgentText) {
|
|
@@ -18722,6 +19074,22 @@ var SddWizardWebSocketHandler = class {
|
|
|
18722
19074
|
async handleMessage(msg) {
|
|
18723
19075
|
try {
|
|
18724
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
|
+
}
|
|
18725
19093
|
switch (msg.type) {
|
|
18726
19094
|
case "sdd.spec.start":
|
|
18727
19095
|
await this.onStart(String(msg.payload?.goal ?? "").trim(), {
|
|
@@ -18738,7 +19106,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
18738
19106
|
if (this.driver) {
|
|
18739
19107
|
this.broadcast(this.snapshotMsg());
|
|
18740
19108
|
if (this.lastAgentText) {
|
|
18741
|
-
this.broadcast({
|
|
19109
|
+
this.broadcast({
|
|
19110
|
+
type: "sdd.spec.agent_text",
|
|
19111
|
+
payload: { text: this.lastAgentText }
|
|
19112
|
+
});
|
|
18742
19113
|
}
|
|
18743
19114
|
}
|
|
18744
19115
|
break;
|
|
@@ -18952,10 +19323,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
18952
19323
|
|
|
18953
19324
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
18954
19325
|
import { watch as fsWatch } from "node:fs";
|
|
18955
|
-
import * as
|
|
19326
|
+
import * as path24 from "node:path";
|
|
18956
19327
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
18957
19328
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
18958
|
-
const globalRoot = globalConfigPath ?
|
|
19329
|
+
const globalRoot = globalConfigPath ? path24.dirname(globalConfigPath) : void 0;
|
|
18959
19330
|
if (!globalRoot) return void 0;
|
|
18960
19331
|
const disposers = [];
|
|
18961
19332
|
const broadcastSessions = async () => {
|
|
@@ -18965,8 +19336,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
18965
19336
|
const sessions = await registry.list();
|
|
18966
19337
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
18967
19338
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
18968
|
-
const myRoot =
|
|
18969
|
-
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) => ({
|
|
18970
19341
|
sessionId: s.sessionId,
|
|
18971
19342
|
projectName: s.projectName,
|
|
18972
19343
|
projectSlug: s.projectSlug,
|
|
@@ -19156,14 +19527,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
19156
19527
|
|
|
19157
19528
|
// src/server/setup-events-status-watcher.ts
|
|
19158
19529
|
import { watch as fsWatch2 } from "node:fs";
|
|
19159
|
-
import * as
|
|
19160
|
-
import * as
|
|
19530
|
+
import * as fs19 from "node:fs/promises";
|
|
19531
|
+
import * as path26 from "node:path";
|
|
19161
19532
|
|
|
19162
19533
|
// src/server/setup-events-watcher.ts
|
|
19163
|
-
import * as
|
|
19534
|
+
import * as path25 from "node:path";
|
|
19164
19535
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
19165
19536
|
const raw = String(filename);
|
|
19166
|
-
const relative5 =
|
|
19537
|
+
const relative5 = path25.isAbsolute(raw) ? path25.relative(projectsDir, raw) : raw;
|
|
19167
19538
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
19168
19539
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
19169
19540
|
return parts.at(-2) ?? null;
|
|
@@ -19198,7 +19569,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
19198
19569
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
19199
19570
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
19200
19571
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
19201
|
-
const projectsDir =
|
|
19572
|
+
const projectsDir = path26.join(wpaths.globalRoot, "projects");
|
|
19202
19573
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
19203
19574
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
19204
19575
|
const DEBOUNCE_MS2 = 150;
|
|
@@ -19243,7 +19614,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19243
19614
|
let watcher;
|
|
19244
19615
|
const startWatcher = async () => {
|
|
19245
19616
|
try {
|
|
19246
|
-
await
|
|
19617
|
+
await fs19.mkdir(projectsDir, { recursive: true });
|
|
19247
19618
|
if (isDisposed()) return;
|
|
19248
19619
|
watcher = fsWatch2(
|
|
19249
19620
|
projectsDir,
|
|
@@ -19257,8 +19628,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19257
19628
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
19258
19629
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
19259
19630
|
try {
|
|
19260
|
-
const targetFile =
|
|
19261
|
-
const content = await
|
|
19631
|
+
const targetFile = path26.join(projectsDir, projectHash, "status.json");
|
|
19632
|
+
const content = await fs19.readFile(targetFile, "utf-8");
|
|
19262
19633
|
const statusData = JSON.parse(content);
|
|
19263
19634
|
scheduleBroadcast(projectHash, statusData);
|
|
19264
19635
|
} catch {
|
|
@@ -19315,8 +19686,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19315
19686
|
}
|
|
19316
19687
|
|
|
19317
19688
|
// src/server/setup-events-core-watchers.ts
|
|
19318
|
-
import * as
|
|
19319
|
-
import * as
|
|
19689
|
+
import * as fs20 from "node:fs/promises";
|
|
19690
|
+
import * as path27 from "node:path";
|
|
19320
19691
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
19321
19692
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
19322
19693
|
const disposers = [];
|
|
@@ -19352,9 +19723,9 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
19352
19723
|
if (wpaths?.projectStatus) {
|
|
19353
19724
|
try {
|
|
19354
19725
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
19355
|
-
const dir =
|
|
19356
|
-
await
|
|
19357
|
-
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");
|
|
19358
19729
|
} catch (err) {
|
|
19359
19730
|
console.error(
|
|
19360
19731
|
JSON.stringify({
|
|
@@ -20379,7 +20750,7 @@ var SpecsWebSocketHandler = class {
|
|
|
20379
20750
|
// src/server/start-webui.ts
|
|
20380
20751
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
20381
20752
|
import * as http2 from "node:http";
|
|
20382
|
-
import * as
|
|
20753
|
+
import * as path34 from "node:path";
|
|
20383
20754
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
20384
20755
|
import { getSharedProjectMailbox as getSharedProjectMailbox5, resolveProjectDir as resolveProjectDir4 } from "@wrongstack/core/coordination";
|
|
20385
20756
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
@@ -20393,7 +20764,7 @@ import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } fro
|
|
|
20393
20764
|
import {
|
|
20394
20765
|
expectDefined as expectDefined4,
|
|
20395
20766
|
sessionScopedPath as sessionScopedPath3,
|
|
20396
|
-
|
|
20767
|
+
startSharedHeapWatchdog,
|
|
20397
20768
|
toErrorMessage as toErrorMessage14,
|
|
20398
20769
|
wstackGlobalRoot as wstackGlobalRoot4
|
|
20399
20770
|
} from "@wrongstack/core/utils";
|
|
@@ -20402,10 +20773,8 @@ import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
|
20402
20773
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
20403
20774
|
|
|
20404
20775
|
// src/server/backend-services.ts
|
|
20405
|
-
import { join as
|
|
20406
|
-
import {
|
|
20407
|
-
Agent
|
|
20408
|
-
} from "@wrongstack/core/agent";
|
|
20776
|
+
import { join as join16 } from "node:path";
|
|
20777
|
+
import { Agent } from "@wrongstack/core/agent";
|
|
20409
20778
|
import {
|
|
20410
20779
|
BrainDecisionLedger,
|
|
20411
20780
|
BrainMonitor,
|
|
@@ -20417,8 +20786,8 @@ import {
|
|
|
20417
20786
|
mailboxSessionTag,
|
|
20418
20787
|
ObservableBrainArbiter as ObservableBrainArbiterCtor
|
|
20419
20788
|
} from "@wrongstack/core/coordination";
|
|
20420
|
-
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
20421
20789
|
import { DEFAULT_TOOLS_CONFIG } from "@wrongstack/core/defaults";
|
|
20790
|
+
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
20422
20791
|
import {
|
|
20423
20792
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
20424
20793
|
createBrainRuntime,
|
|
@@ -20428,7 +20797,9 @@ import {
|
|
|
20428
20797
|
} from "@wrongstack/core/execution";
|
|
20429
20798
|
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
20430
20799
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
20431
|
-
import {
|
|
20800
|
+
import {
|
|
20801
|
+
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
20802
|
+
} from "@wrongstack/core/types";
|
|
20432
20803
|
import {
|
|
20433
20804
|
estimateRequestTokensCalibrated,
|
|
20434
20805
|
toErrorMessage as toErrorMessage10
|
|
@@ -20445,7 +20816,7 @@ import {
|
|
|
20445
20816
|
import { spawn as spawn4 } from "node:child_process";
|
|
20446
20817
|
import { createRequire } from "node:module";
|
|
20447
20818
|
import { existsSync as existsSync2 } from "node:fs";
|
|
20448
|
-
import { dirname as dirname10, join as
|
|
20819
|
+
import { dirname as dirname10, join as join14 } from "node:path";
|
|
20449
20820
|
import { resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
|
|
20450
20821
|
import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
|
|
20451
20822
|
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
@@ -20574,7 +20945,7 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
20574
20945
|
function findWorkspaceCliEntry(projectRoot) {
|
|
20575
20946
|
let dir = projectRoot;
|
|
20576
20947
|
for (let i = 0; i < 6; i++) {
|
|
20577
|
-
const candidate =
|
|
20948
|
+
const candidate = join14(dir, "packages", "cli", "dist", "index.js");
|
|
20578
20949
|
if (existsSync2(candidate)) return candidate;
|
|
20579
20950
|
const parent = dirname10(dir);
|
|
20580
20951
|
if (parent === dir) return null;
|
|
@@ -20819,10 +21190,10 @@ function clampDim(value, fallback) {
|
|
|
20819
21190
|
}
|
|
20820
21191
|
|
|
20821
21192
|
// src/server/worktree-ws-handler.ts
|
|
20822
|
-
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";
|
|
20823
21195
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
20824
21196
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
20825
|
-
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
20826
21197
|
var MAX_ACTIVITY = 6;
|
|
20827
21198
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
|
|
20828
21199
|
var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
|
|
@@ -20841,6 +21212,20 @@ var WorktreeWebSocketHandler = class {
|
|
|
20841
21212
|
baseBranch = "";
|
|
20842
21213
|
broadcastInterval = null;
|
|
20843
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;
|
|
20844
21229
|
addClient(ws) {
|
|
20845
21230
|
this.clients.add(ws);
|
|
20846
21231
|
ws.on("close", () => this.clients.delete(ws));
|
|
@@ -20859,7 +21244,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
20859
21244
|
return true;
|
|
20860
21245
|
}
|
|
20861
21246
|
if (msg.type === "worktree.remove") {
|
|
20862
|
-
await this.removeOne(
|
|
21247
|
+
await this.removeOne(
|
|
21248
|
+
msg.payload?.["dir"],
|
|
21249
|
+
msg.payload?.["branch"]
|
|
21250
|
+
);
|
|
20863
21251
|
return true;
|
|
20864
21252
|
}
|
|
20865
21253
|
if (msg.type === "worktree.merge") {
|
|
@@ -20867,7 +21255,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
20867
21255
|
return true;
|
|
20868
21256
|
}
|
|
20869
21257
|
if (msg.type === "worktree.diff") {
|
|
20870
|
-
await this.diffOne(
|
|
21258
|
+
await this.diffOne(
|
|
21259
|
+
msg.payload?.["dir"],
|
|
21260
|
+
msg.payload?.["baseBranch"]
|
|
21261
|
+
);
|
|
20871
21262
|
return true;
|
|
20872
21263
|
}
|
|
20873
21264
|
return false;
|
|
@@ -20880,7 +21271,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
20880
21271
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
20881
21272
|
/** Absolute managed-worktrees root for this project. */
|
|
20882
21273
|
worktreesRoot() {
|
|
20883
|
-
return resolve13(
|
|
21274
|
+
return resolve13(join15(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
20884
21275
|
}
|
|
20885
21276
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
20886
21277
|
underRoot(dir) {
|
|
@@ -20896,12 +21287,43 @@ var WorktreeWebSocketHandler = class {
|
|
|
20896
21287
|
}
|
|
20897
21288
|
return live;
|
|
20898
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
|
+
}
|
|
20899
21321
|
/**
|
|
20900
21322
|
* Scan the disk for managed worktrees/branches NOT owned by a live in-session
|
|
20901
21323
|
* run and broadcast them as orphans, with whether it is safe to clean now.
|
|
20902
21324
|
* No-op (empty inventory) when management deps were not wired.
|
|
20903
21325
|
*/
|
|
20904
|
-
async
|
|
21326
|
+
async runScanOnce() {
|
|
20905
21327
|
if (!this.management) {
|
|
20906
21328
|
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
20907
21329
|
return;
|
|
@@ -20957,7 +21379,8 @@ var WorktreeWebSocketHandler = class {
|
|
|
20957
21379
|
}
|
|
20958
21380
|
const res = await cleanupStaleSddWorktrees2({
|
|
20959
21381
|
projectRoot: this.management.projectRoot,
|
|
20960
|
-
boardsDir: this.management.boardsDir
|
|
21382
|
+
boardsDir: this.management.boardsDir,
|
|
21383
|
+
stateTransport: "kanban"
|
|
20961
21384
|
});
|
|
20962
21385
|
if (res.skippedReason) {
|
|
20963
21386
|
this.broadcast({
|
|
@@ -20970,26 +21393,45 @@ var WorktreeWebSocketHandler = class {
|
|
|
20970
21393
|
for (const [id, h] of [...this.handles]) {
|
|
20971
21394
|
if (!ACTIVE_STATUSES.has(h.status)) this.handles.delete(id);
|
|
20972
21395
|
}
|
|
20973
|
-
this.broadcast({
|
|
21396
|
+
this.broadcast({
|
|
21397
|
+
type: "worktree.cleanup_result",
|
|
21398
|
+
payload: { ok: true, removed: res.removed }
|
|
21399
|
+
});
|
|
20974
21400
|
this.broadcastState();
|
|
20975
21401
|
await this.scanAndBroadcast();
|
|
20976
21402
|
}
|
|
20977
21403
|
/** Remove/discard ONE worktree + branch. Refused while a live run owns it. */
|
|
20978
21404
|
async removeOne(dir, branch) {
|
|
20979
21405
|
if (!this.management || !dir && !branch) {
|
|
20980
|
-
this.broadcast({
|
|
21406
|
+
this.broadcast({
|
|
21407
|
+
type: "worktree.cleanup_result",
|
|
21408
|
+
payload: { ok: false, removed: 0, reason: "nothing to remove" }
|
|
21409
|
+
});
|
|
20981
21410
|
return;
|
|
20982
21411
|
}
|
|
20983
21412
|
if (branch && !MANAGED_BRANCH_RE.test(branch)) {
|
|
20984
|
-
this.broadcast({
|
|
21413
|
+
this.broadcast({
|
|
21414
|
+
type: "worktree.cleanup_result",
|
|
21415
|
+
payload: { ok: false, removed: 0, reason: "not a managed worktree branch" }
|
|
21416
|
+
});
|
|
20985
21417
|
return;
|
|
20986
21418
|
}
|
|
20987
21419
|
if (dir && !this.underRoot(dir)) {
|
|
20988
|
-
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
|
+
});
|
|
20989
21424
|
return;
|
|
20990
21425
|
}
|
|
20991
21426
|
if (branch && this.liveActiveBranches().has(branch)) {
|
|
20992
|
-
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
|
+
});
|
|
20993
21435
|
return;
|
|
20994
21436
|
}
|
|
20995
21437
|
let removed = false;
|
|
@@ -20998,31 +21440,54 @@ var WorktreeWebSocketHandler = class {
|
|
|
20998
21440
|
({ removed } = await wt.removeOne(dir, branch));
|
|
20999
21441
|
}
|
|
21000
21442
|
for (const [id, h] of [...this.handles]) {
|
|
21001
|
-
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);
|
|
21002
21445
|
}
|
|
21003
|
-
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
|
+
});
|
|
21004
21454
|
this.broadcastState();
|
|
21005
21455
|
await this.scanAndBroadcast();
|
|
21006
21456
|
}
|
|
21007
21457
|
/** Squash-merge ONE branch into base. Refused while a live run owns it. */
|
|
21008
21458
|
async mergeBranch(branch) {
|
|
21009
21459
|
if (!this.management || !branch) {
|
|
21010
|
-
this.broadcast({
|
|
21460
|
+
this.broadcast({
|
|
21461
|
+
type: "worktree.merge_result",
|
|
21462
|
+
payload: { ok: false, branch: branch ?? "", reason: "no branch" }
|
|
21463
|
+
});
|
|
21011
21464
|
return;
|
|
21012
21465
|
}
|
|
21013
21466
|
if (!MANAGED_BRANCH_RE.test(branch)) {
|
|
21014
|
-
this.broadcast({
|
|
21467
|
+
this.broadcast({
|
|
21468
|
+
type: "worktree.merge_result",
|
|
21469
|
+
payload: { ok: false, branch, reason: "not a managed worktree branch" }
|
|
21470
|
+
});
|
|
21015
21471
|
return;
|
|
21016
21472
|
}
|
|
21017
21473
|
if (this.liveActiveBranches().has(branch)) {
|
|
21018
|
-
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
|
+
});
|
|
21019
21478
|
return;
|
|
21020
21479
|
}
|
|
21021
21480
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
21022
21481
|
const res = await wt.mergeBranch(branch);
|
|
21023
21482
|
this.broadcast({
|
|
21024
21483
|
type: "worktree.merge_result",
|
|
21025
|
-
payload: {
|
|
21484
|
+
payload: {
|
|
21485
|
+
ok: res.ok,
|
|
21486
|
+
branch,
|
|
21487
|
+
conflict: res.conflict,
|
|
21488
|
+
conflictFiles: res.conflictFiles,
|
|
21489
|
+
reason: res.reason
|
|
21490
|
+
}
|
|
21026
21491
|
});
|
|
21027
21492
|
await this.scanAndBroadcast();
|
|
21028
21493
|
}
|
|
@@ -21064,8 +21529,14 @@ var WorktreeWebSocketHandler = class {
|
|
|
21064
21529
|
}),
|
|
21065
21530
|
on("worktree.committed", (p) => {
|
|
21066
21531
|
const e = p;
|
|
21067
|
-
this.patch(e.handleId, {
|
|
21068
|
-
|
|
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)`);
|
|
21069
21540
|
this.broadcastState();
|
|
21070
21541
|
}),
|
|
21071
21542
|
on("worktree.merged", (p) => {
|
|
@@ -21106,10 +21577,15 @@ var WorktreeWebSocketHandler = class {
|
|
|
21106
21577
|
activity(id, kind, text2) {
|
|
21107
21578
|
const cur = this.handles.get(id);
|
|
21108
21579
|
if (cur) {
|
|
21109
|
-
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
|
+
);
|
|
21110
21583
|
this.handles.set(id, { ...cur, recentActivity });
|
|
21111
21584
|
}
|
|
21112
|
-
this.broadcast({
|
|
21585
|
+
this.broadcast({
|
|
21586
|
+
type: "worktree.event",
|
|
21587
|
+
payload: { kind, handleId: id, text: text2, at: Date.now() }
|
|
21588
|
+
});
|
|
21113
21589
|
}
|
|
21114
21590
|
stateMessage() {
|
|
21115
21591
|
return {
|
|
@@ -21351,7 +21827,7 @@ async function createAgentServices(input) {
|
|
|
21351
21827
|
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
21352
21828
|
fallbackModels: config.fallbackModels
|
|
21353
21829
|
});
|
|
21354
|
-
const brainLedgerPath =
|
|
21830
|
+
const brainLedgerPath = join16(wpaths.projectDir, "brain-ledger.jsonl");
|
|
21355
21831
|
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
21356
21832
|
let brainLedger;
|
|
21357
21833
|
const startBrainLedger = () => {
|
|
@@ -21416,33 +21892,35 @@ async function createAgentServices(input) {
|
|
|
21416
21892
|
brainLog.push(entry);
|
|
21417
21893
|
if (brainLog.length > 20) brainLog.shift();
|
|
21418
21894
|
};
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
|
|
21436
|
-
|
|
21437
|
-
|
|
21438
|
-
|
|
21439
|
-
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
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
|
+
];
|
|
21446
21924
|
const brainMailbox = getSharedProjectMailbox3(wpaths.projectDir, events);
|
|
21447
21925
|
brainMonitor = new BrainMonitor({
|
|
21448
21926
|
events,
|
|
@@ -21554,6 +22032,17 @@ async function createAgentServices(input) {
|
|
|
21554
22032
|
getActiveSessionId: () => context.session.id
|
|
21555
22033
|
}
|
|
21556
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
|
+
};
|
|
21557
22046
|
return {
|
|
21558
22047
|
collabBus,
|
|
21559
22048
|
compactor,
|
|
@@ -21579,6 +22068,7 @@ async function createAgentServices(input) {
|
|
|
21579
22068
|
worktreeHandler,
|
|
21580
22069
|
terminalHandler,
|
|
21581
22070
|
collabHandler,
|
|
22071
|
+
disposeRealtimeHandlers,
|
|
21582
22072
|
updateAutoCompactionMaxContext
|
|
21583
22073
|
};
|
|
21584
22074
|
}
|
|
@@ -21665,7 +22155,7 @@ function createConnectionHandler(options) {
|
|
|
21665
22155
|
}
|
|
21666
22156
|
|
|
21667
22157
|
// src/server/message-dispatcher.ts
|
|
21668
|
-
import
|
|
22158
|
+
import path28 from "node:path";
|
|
21669
22159
|
function createMessageDispatcher(opts) {
|
|
21670
22160
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
21671
22161
|
function makeWorklistContext() {
|
|
@@ -21686,7 +22176,7 @@ function createMessageDispatcher(opts) {
|
|
|
21686
22176
|
skillLoader: deps2.skillLoader,
|
|
21687
22177
|
skillInstaller: deps2.skillInstaller,
|
|
21688
22178
|
projectRoot,
|
|
21689
|
-
projectSkillsDir:
|
|
22179
|
+
projectSkillsDir: path28.join(projectRoot, ".wrongstack", "skills"),
|
|
21690
22180
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
21691
22181
|
};
|
|
21692
22182
|
}
|
|
@@ -21938,7 +22428,7 @@ function createMessageDispatcher(opts) {
|
|
|
21938
22428
|
|
|
21939
22429
|
// src/server/pre-context-services.ts
|
|
21940
22430
|
import { createRequire as createRequire3 } from "node:module";
|
|
21941
|
-
import * as
|
|
22431
|
+
import * as path31 from "node:path";
|
|
21942
22432
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
21943
22433
|
import {
|
|
21944
22434
|
getSharedProjectMailbox as getSharedProjectMailbox4,
|
|
@@ -21992,8 +22482,8 @@ import { configureDangerBypass, configureExecPolicy } from "@wrongstack/tools";
|
|
|
21992
22482
|
import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
|
|
21993
22483
|
|
|
21994
22484
|
// src/server/model-auto-discovery.ts
|
|
21995
|
-
import * as
|
|
21996
|
-
import * as
|
|
22485
|
+
import * as fs21 from "node:fs/promises";
|
|
22486
|
+
import * as path29 from "node:path";
|
|
21997
22487
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
21998
22488
|
function isOverlayRegistry(value) {
|
|
21999
22489
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -22019,7 +22509,7 @@ function eligibleProviders(config) {
|
|
|
22019
22509
|
}
|
|
22020
22510
|
async function readCache(file) {
|
|
22021
22511
|
try {
|
|
22022
|
-
return JSON.parse(await
|
|
22512
|
+
return JSON.parse(await fs21.readFile(file, "utf8"));
|
|
22023
22513
|
} catch {
|
|
22024
22514
|
return {};
|
|
22025
22515
|
}
|
|
@@ -22029,7 +22519,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
22029
22519
|
if (!isOverlayRegistry(registry)) return;
|
|
22030
22520
|
const targets = eligibleProviders(opts.config);
|
|
22031
22521
|
if (targets.length === 0) return;
|
|
22032
|
-
const cacheFile =
|
|
22522
|
+
const cacheFile = path29.join(opts.cacheDir, "discovered-models-cache.json");
|
|
22033
22523
|
const cache2 = await readCache(cacheFile);
|
|
22034
22524
|
let cacheDirty = false;
|
|
22035
22525
|
await Promise.all(
|
|
@@ -22066,8 +22556,8 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
22066
22556
|
);
|
|
22067
22557
|
if (cacheDirty) {
|
|
22068
22558
|
try {
|
|
22069
|
-
await
|
|
22070
|
-
await
|
|
22559
|
+
await fs21.mkdir(path29.dirname(cacheFile), { recursive: true });
|
|
22560
|
+
await fs21.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
22071
22561
|
} catch {
|
|
22072
22562
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
22073
22563
|
}
|
|
@@ -22163,7 +22653,7 @@ function resolveSetupProvider(opts) {
|
|
|
22163
22653
|
}
|
|
22164
22654
|
|
|
22165
22655
|
// src/server/standalone-session-identity.ts
|
|
22166
|
-
import * as
|
|
22656
|
+
import * as path30 from "node:path";
|
|
22167
22657
|
import {
|
|
22168
22658
|
AgentStatusTracker,
|
|
22169
22659
|
FleetNotifier,
|
|
@@ -22182,7 +22672,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
22182
22672
|
let activeTarget = {
|
|
22183
22673
|
projectSlug: paths.projectSlug,
|
|
22184
22674
|
projectRoot: paths.projectRoot,
|
|
22185
|
-
projectName:
|
|
22675
|
+
projectName: path30.basename(paths.projectRoot),
|
|
22186
22676
|
workingDir: opts.workingDir
|
|
22187
22677
|
};
|
|
22188
22678
|
let pendingClaim;
|
|
@@ -22413,7 +22903,7 @@ async function createPreContextServices(input) {
|
|
|
22413
22903
|
await discoverAndMergeWebuiProviders({
|
|
22414
22904
|
config,
|
|
22415
22905
|
registry: modelsRegistry,
|
|
22416
|
-
cacheDir:
|
|
22906
|
+
cacheDir: path31.dirname(wpaths.modelsCache),
|
|
22417
22907
|
logger
|
|
22418
22908
|
});
|
|
22419
22909
|
} catch (err) {
|
|
@@ -22465,7 +22955,7 @@ async function createPreContextServices(input) {
|
|
|
22465
22955
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
22466
22956
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
22467
22957
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
22468
|
-
|
|
22958
|
+
path31.join(wpaths.projectDir, "mcp-auth.json"),
|
|
22469
22959
|
vault
|
|
22470
22960
|
);
|
|
22471
22961
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -22571,7 +23061,7 @@ async function createPreContextServices(input) {
|
|
|
22571
23061
|
};
|
|
22572
23062
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
22573
23063
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
22574
|
-
manifestPath:
|
|
23064
|
+
manifestPath: path31.join(wpaths.configDir, "installed-skills.json"),
|
|
22575
23065
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
22576
23066
|
globalSkillsDir: wpaths.globalSkills,
|
|
22577
23067
|
projectHash: wpaths.projectHash,
|
|
@@ -22581,8 +23071,8 @@ async function createPreContextServices(input) {
|
|
|
22581
23071
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
22582
23072
|
try {
|
|
22583
23073
|
const req = createRequire3(import.meta.url);
|
|
22584
|
-
return
|
|
22585
|
-
|
|
23074
|
+
return path31.join(
|
|
23075
|
+
path31.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
22586
23076
|
"data",
|
|
22587
23077
|
"prompts"
|
|
22588
23078
|
);
|
|
@@ -22686,7 +23176,7 @@ async function createPreContextServices(input) {
|
|
|
22686
23176
|
}
|
|
22687
23177
|
|
|
22688
23178
|
// src/server/routes.ts
|
|
22689
|
-
import
|
|
23179
|
+
import path32 from "node:path";
|
|
22690
23180
|
import { makeProviderFromConfig as makeProviderFromConfig4, withCatalogCapabilities } from "@wrongstack/providers";
|
|
22691
23181
|
|
|
22692
23182
|
// src/server/mode-handlers.ts
|
|
@@ -22978,7 +23468,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
22978
23468
|
};
|
|
22979
23469
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
22980
23470
|
getProjectRoot: state.getProjectRoot,
|
|
22981
|
-
getGlobalRoot: () =>
|
|
23471
|
+
getGlobalRoot: () => path32.dirname(deps2.globalConfigPath),
|
|
22982
23472
|
events: deps2.events
|
|
22983
23473
|
});
|
|
22984
23474
|
const mcpRoutes = {
|
|
@@ -23051,7 +23541,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
23051
23541
|
}
|
|
23052
23542
|
|
|
23053
23543
|
// src/server/server-runtime.ts
|
|
23054
|
-
import * as
|
|
23544
|
+
import * as path33 from "node:path";
|
|
23055
23545
|
import { createRequire as createRequire4 } from "node:module";
|
|
23056
23546
|
import { fileURLToPath } from "node:url";
|
|
23057
23547
|
import { WebSocketServer } from "ws";
|
|
@@ -23112,7 +23602,7 @@ function createSessionStartPayload(g) {
|
|
|
23112
23602
|
inputCost,
|
|
23113
23603
|
outputCost,
|
|
23114
23604
|
cacheReadCost,
|
|
23115
|
-
projectName:
|
|
23605
|
+
projectName: path33.basename(projectRoot) || projectRoot,
|
|
23116
23606
|
projectRoot,
|
|
23117
23607
|
cwd: g.getWorkingDir(),
|
|
23118
23608
|
mode: g.getModeId(),
|
|
@@ -23200,13 +23690,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
23200
23690
|
};
|
|
23201
23691
|
}
|
|
23202
23692
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
23203
|
-
if (explicitDistDir) return
|
|
23693
|
+
if (explicitDistDir) return path33.resolve(explicitDistDir);
|
|
23204
23694
|
try {
|
|
23205
23695
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
23206
23696
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
23207
|
-
return
|
|
23697
|
+
return path33.dirname(serverEntry);
|
|
23208
23698
|
} catch {
|
|
23209
|
-
return
|
|
23699
|
+
return path33.resolve(path33.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
23210
23700
|
}
|
|
23211
23701
|
}
|
|
23212
23702
|
function startHttpServer(opts) {
|
|
@@ -23420,6 +23910,7 @@ async function startWebUI(opts = {}) {
|
|
|
23420
23910
|
worktreeHandler,
|
|
23421
23911
|
terminalHandler,
|
|
23422
23912
|
collabHandler,
|
|
23913
|
+
disposeRealtimeHandlers,
|
|
23423
23914
|
updateAutoCompactionMaxContext
|
|
23424
23915
|
} = agentServices;
|
|
23425
23916
|
if (typeof context.meta["yolo"] === "boolean") {
|
|
@@ -23598,21 +24089,21 @@ async function startWebUI(opts = {}) {
|
|
|
23598
24089
|
});
|
|
23599
24090
|
}
|
|
23600
24091
|
async function touchProjectEntry(root, workDir) {
|
|
23601
|
-
const resolved =
|
|
24092
|
+
const resolved = path34.resolve(root);
|
|
23602
24093
|
const manifest = await loadManifest(globalConfigPath);
|
|
23603
24094
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23604
|
-
const existing = manifest.projects.find((p) =>
|
|
24095
|
+
const existing = manifest.projects.find((p) => path34.resolve(p.root) === resolved);
|
|
23605
24096
|
if (existing) {
|
|
23606
24097
|
existing.lastSeen = now;
|
|
23607
|
-
if (workDir) existing.lastWorkingDir =
|
|
24098
|
+
if (workDir) existing.lastWorkingDir = path34.resolve(workDir);
|
|
23608
24099
|
} else {
|
|
23609
24100
|
manifest.projects.push({
|
|
23610
|
-
name:
|
|
24101
|
+
name: path34.basename(resolved),
|
|
23611
24102
|
root: resolved,
|
|
23612
24103
|
slug: generateProjectSlug(resolved),
|
|
23613
24104
|
createdAt: now,
|
|
23614
24105
|
lastSeen: now,
|
|
23615
|
-
lastWorkingDir: workDir ?
|
|
24106
|
+
lastWorkingDir: workDir ? path34.resolve(workDir) : void 0
|
|
23616
24107
|
});
|
|
23617
24108
|
}
|
|
23618
24109
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -23789,7 +24280,20 @@ async function startWebUI(opts = {}) {
|
|
|
23789
24280
|
);
|
|
23790
24281
|
credentialWatcherClose = credentialWatcher.close;
|
|
23791
24282
|
}
|
|
23792
|
-
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
|
+
});
|
|
23793
24297
|
const routes = buildRoutes(state, deps2, cb);
|
|
23794
24298
|
const handleMessage = createMessageDispatcher({
|
|
23795
24299
|
state,
|
|
@@ -23867,6 +24371,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23867
24371
|
await todosCheckpoint.detach();
|
|
23868
24372
|
await stopHeapWatchdog();
|
|
23869
24373
|
credentialWatcherClose?.();
|
|
24374
|
+
disposeRealtimeHandlers();
|
|
23870
24375
|
brainMonitor.stop();
|
|
23871
24376
|
await agentServices.brainLedger?.stop().catch(() => {
|
|
23872
24377
|
});
|
|
@@ -23890,7 +24395,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
23890
24395
|
await memoryStore.dispose().catch(
|
|
23891
24396
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage14(err)}`)
|
|
23892
24397
|
);
|
|
23893
|
-
await unregisterInstance(process.pid,
|
|
24398
|
+
await unregisterInstance(process.pid, path34.dirname(globalConfigPath));
|
|
23894
24399
|
}
|
|
23895
24400
|
});
|
|
23896
24401
|
}
|